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.
- package/CHANGELOG.md +70 -0
- package/LICENSE +21 -0
- package/MIGRATING.md +89 -0
- package/README.md +308 -105
- package/dist/core/abi.d.ts +10930 -0
- package/dist/core/abi.js +455 -0
- package/dist/core/advisories.d.ts +184 -0
- package/dist/core/advisories.js +291 -0
- package/dist/core/client.d.ts +55 -0
- package/dist/core/client.js +107 -0
- package/dist/core/generated.d.ts +14295 -0
- package/dist/core/generated.js +1058 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +17 -0
- package/dist/core/launch.d.ts +200 -0
- package/dist/core/launch.js +427 -0
- package/dist/core/pair.d.ts +64 -0
- package/dist/core/pair.js +136 -0
- package/dist/core/params.d.ts +415 -0
- package/dist/core/params.js +1235 -0
- package/dist/core/policies.d.ts +136 -0
- package/dist/core/policies.js +125 -0
- package/dist/core/proofs.d.ts +25 -0
- package/dist/core/proofs.js +118 -0
- package/dist/core/quotes.d.ts +185 -0
- package/dist/core/quotes.js +550 -0
- package/dist/core/referrals.d.ts +32 -0
- package/dist/core/referrals.js +36 -0
- package/dist/core/registry.d.ts +357 -0
- package/dist/core/registry.js +378 -0
- package/dist/core/rules.d.ts +675 -0
- package/dist/core/rules.js +1049 -0
- package/dist/core/schema.d.ts +358 -0
- package/dist/core/schema.js +105 -0
- package/dist/core/stocks.generated.d.ts +11 -0
- package/dist/core/stocks.generated.js +1228 -0
- package/dist/core/vault.d.ts +59 -0
- package/dist/core/vault.js +112 -0
- package/dist/core/wallet.d.ts +142 -0
- package/dist/core/wallet.js +304 -0
- package/dist/deployments.d.ts +8 -0
- package/dist/deployments.js +26 -0
- package/dist/index.d.ts +42 -15
- package/dist/index.js +40 -15
- package/dist/releases.d.ts +219 -0
- package/dist/releases.js +365 -0
- package/dist/wallet-cap.d.ts +159 -0
- package/dist/wallet-cap.js +234 -0
- package/package.json +46 -10
- package/provenance.json +31 -0
- package/dist/abi.d.ts +0 -6404
- package/dist/abi.js +0 -8300
- package/dist/chain.d.ts +0 -87
- package/dist/chain.js +0 -86
- package/dist/claims.d.ts +0 -32
- package/dist/claims.js +0 -31
- package/dist/fees.d.ts +0 -59
- package/dist/fees.js +0 -142
- package/dist/links.d.ts +0 -48
- package/dist/links.js +0 -93
- package/dist/market.d.ts +0 -264
- package/dist/market.js +0 -310
- package/dist/reads.d.ts +0 -136
- package/dist/reads.js +0 -219
- package/dist/rules.d.ts +0 -84
- package/dist/rules.js +0 -296
- package/dist/swap.d.ts +0 -108
- package/dist/swap.js +0 -144
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# hookr-sdk
|
|
2
2
|
|
|
3
|
-
Build
|
|
4
|
-
through Hookr's router, read markets, and claim payouts, on Robinhood Chain (id 4663).
|
|
3
|
+
Build on Hookr, the Uniswap v4 hook on Robinhood Chain: launch tokens with Hook Blocks, open pools, quote and swap.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
The package carries Hookr's contracts, release id `hookr-1`: their addresses, deployment and ABIs, the 22-quote catalog, the bounds of every setting of the core Hook Blocks and advisories, and the builders hookr.fun itself uses. It holds no wallet and signs nothing. Each builder returns `{ chainId, to, data, value }` for your wallet to send.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
- [Install](#install)
|
|
8
|
+
- [Quick start](#quick-start)
|
|
9
|
+
- [Addresses](#addresses)
|
|
10
|
+
- [Wallet Cap](#wallet-cap)
|
|
11
|
+
- [Arb recapture](#arb-recapture)
|
|
12
|
+
- [Gas](#gas)
|
|
13
|
+
- [Quotes](#quotes)
|
|
14
|
+
- [What is in the package](#what-is-in-the-package)
|
|
15
15
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
@@ -19,135 +19,338 @@ a root from the pool's own `PoolKey.hooks` or from the registry rather than from
|
|
|
19
19
|
npm install hookr-sdk viem
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
ESM only, with TypeScript types. Node 18.16 or newer and viem 2. `ROBINHOOD_CHAIN` is the chain for viem, with RobinScan as its explorer. On viem 2.55 or newer, `robinhood` from `viem/chains` is the same chain with Blockscout as its explorer.
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
### Set up
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
limits, and a wide `eth_getLogs` can come back as an empty array instead of an error. `listMarkets`
|
|
26
|
-
scans in windows of `MARKET_SCAN_CHUNK_BLOCKS` (3,000,000 blocks, the width the app's own scanners
|
|
27
|
-
use; pass `chunkBlocks` to lower it) so no single answer is wide, but anything that must not miss
|
|
28
|
-
data should still run on a provider endpoint you own:
|
|
28
|
+
Every builder takes the release's manifest: each contract it calls, at its address, with its runtime code hash. `readReleaseManifest` reads that code from chain and refuses it unless every contract runs the code the release deployed.
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
|
-
import
|
|
32
|
-
import {
|
|
31
|
+
import "viem/window";
|
|
32
|
+
import { createPublicClient, createWalletClient, custom, http } from "viem";
|
|
33
|
+
import { HOOKR_1, ROBINHOOD_CHAIN, readReleaseManifest } from "hookr-sdk";
|
|
33
34
|
|
|
34
|
-
const
|
|
35
|
+
const client = createPublicClient({ chain: ROBINHOOD_CHAIN, transport: http() });
|
|
36
|
+
const wallet = createWalletClient({ chain: ROBINHOOD_CHAIN, transport: custom(window.ethereum!) });
|
|
37
|
+
const [account] = await wallet.getAddresses();
|
|
38
|
+
|
|
39
|
+
const manifest = await readReleaseManifest(client, HOOKR_1);
|
|
35
40
|
```
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
`releaseManifest(HOOKR_1)` builds the same manifest from the package alone, with no reads.
|
|
43
|
+
|
|
44
|
+
`http()` uses the chain's public node. It is fine for a few reads. Anything that scans logs or must not miss data should use a provider endpoint of its own.
|
|
45
|
+
|
|
46
|
+
### Read a pool
|
|
47
|
+
|
|
48
|
+
A pool on Hookr's shared hook is a Uniswap v4 pool whose `PoolKey.hooks` is `HookrRoot`. A hook you deploy through the owned-root factory is an unmodified copy of it at an address of its own, and its pools name that address instead. Either way the pool's fee is `DYNAMIC_FEE`: the hook sets the LP fee on every swap.
|
|
38
49
|
|
|
39
50
|
```ts
|
|
40
|
-
import {
|
|
51
|
+
import { DYNAMIC_FEE, HOOKR_1, ZERO, laneRootAbi, poolId, rootAbi, rulesAbi, type PoolKey } from "hookr-sdk";
|
|
52
|
+
|
|
53
|
+
const key: PoolKey = {
|
|
54
|
+
currency0: ZERO, // native ETH sorts first
|
|
55
|
+
currency1: tokenAddress,
|
|
56
|
+
fee: DYNAMIC_FEE,
|
|
57
|
+
tickSpacing: 60,
|
|
58
|
+
hooks: HOOKR_1.contracts.HookrRoot,
|
|
59
|
+
};
|
|
60
|
+
const id = poolId(key);
|
|
61
|
+
|
|
62
|
+
const config = await client.readContract({ address: HOOKR_1.contracts.HookrRoot, abi: rootAbi, functionName: "poolConfig", args: [id] });
|
|
63
|
+
const blocks = await client.readContract({ address: config.rules, abi: rulesAbi, functionName: "config", args: [id] });
|
|
64
|
+
const [executor, codeHash, partnerBps, family, laneOn, gasCap, gasFloor] = await client.readContract({
|
|
65
|
+
address: HOOKR_1.contracts.HookrRoot,
|
|
66
|
+
abi: laneRootAbi,
|
|
67
|
+
functionName: "laneOf",
|
|
68
|
+
args: [id],
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`config` is the pool's `PoolConfig`: its quote, base LP fee and caps, and the HookrRules contract that holds its Hook Blocks, one of the release's [Rules modules](#rules-modules). `blocks` is those Hook Blocks as frozen at launch: Anti-Snipe, the dynamic fee, Auto Burn, LP Rewards, the royalty and the protocol share. `laneOn` says whether arb recapture runs on the pool, and `gasFloor` is the least gas a swap must carry while it does ([Gas](#gas)). On the Wallet Cap Rules, `blocks.potMinBuyQuote` is the pool's [Wallet Cap](#wallet-cap).
|
|
73
|
+
|
|
74
|
+
### Quote
|
|
41
75
|
|
|
42
|
-
|
|
76
|
+
`HookrQuoter` simulates the swap through the pool's own callbacks, so the quote is what the swap pays and gets, fees and Hook Blocks included. Send it as an `eth_call` from the real payer.
|
|
43
77
|
|
|
44
|
-
|
|
45
|
-
|
|
78
|
+
```ts
|
|
79
|
+
import { decodeFunctionResult, parseEther } from "viem";
|
|
80
|
+
import { MIN_SQRT_PRICE_LIMIT, buildQuote, quoterAbi } from "hookr-sdk";
|
|
81
|
+
|
|
82
|
+
const swap = {
|
|
83
|
+
key,
|
|
84
|
+
zeroForOne: true, // ETH in, token out
|
|
85
|
+
amountSpecified: -parseEther("0.1"), // negative: exact input
|
|
86
|
+
amountBound: 1n, // the least out, set from the quote below
|
|
87
|
+
sqrtPriceLimitX96: MIN_SQRT_PRICE_LIMIT, // fill in full; MAX_SQRT_PRICE_LIMIT the other way
|
|
88
|
+
recipient: account,
|
|
89
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 600), // at most an hour ahead
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const quote = buildQuote(manifest, swap, account);
|
|
93
|
+
const { data } = await client.call({ account, to: quote.to, data: quote.data });
|
|
94
|
+
const receipt = decodeFunctionResult({ abi: quoterAbi, functionName: "quote", data: data! });
|
|
95
|
+
// receipt.outputAmount, receipt.quoteFee, receipt.subjectBurn, receipt.lpFeePips
|
|
46
96
|
```
|
|
47
97
|
|
|
48
|
-
|
|
98
|
+
### Swap
|
|
49
99
|
|
|
50
|
-
|
|
51
|
-
root the builder also freezes the five correction fields of the stack (`RECAPTURE_CORRECTION`
|
|
52
|
-
plus the creator), without which the pool would carry that root's name and never correct. The
|
|
53
|
-
registry admits that lane on native-ETH-quoted pools only, so the builder throws on any other
|
|
54
|
-
quote, and it needs `creator` (the launching wallet) to freeze as the correction creator.
|
|
100
|
+
`buildSwap` calls `HookrRouter.swap`. Native ETH in rides as the call's value. An ERC-20 in needs an allowance to the router first: `swapAllowance` says how much, `buildApprovals` builds the calls.
|
|
55
101
|
|
|
56
102
|
```ts
|
|
57
|
-
import {
|
|
58
|
-
import { CHAIN_ID, NATIVE_ADDRESS, ROOTS, buildExistingTokenMarketCall } from "hookr-sdk";
|
|
103
|
+
import { buildSwap } from "hookr-sdk";
|
|
59
104
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
});
|
|
65
|
-
const hash = await walletClient.writeContract(call);
|
|
66
|
-
|
|
67
|
-
// The same market on the recapture root: native ETH quote, and the creator wallet.
|
|
68
|
-
const recaptureCall = buildExistingTokenMarketCall({
|
|
69
|
-
subject: token, quote: NATIVE_ADDRESS, tickSpacing: 60, sqrtPriceX96,
|
|
70
|
-
rules: { baseFeePips: 3_000, blocks: stack },
|
|
71
|
-
protocolRecipient, protocolShareBps, caps,
|
|
72
|
-
root: ROOTS.recapture, creator: account.address,
|
|
73
|
-
});
|
|
105
|
+
const minOut = (receipt.outputAmount * 995n) / 1000n; // 0.5% slippage
|
|
106
|
+
const swapCall = buildSwap(manifest, { ...swap, amountBound: minOut });
|
|
107
|
+
|
|
108
|
+
const estimate = await client.estimateGas({ account, to: swapCall.to, data: swapCall.data, value: swapCall.value });
|
|
109
|
+
const hash = await wallet.sendTransaction({ account, to: swapCall.to, data: swapCall.data, value: swapCall.value, gas: estimate + estimate / 6n });
|
|
74
110
|
```
|
|
75
111
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
112
|
+
When the estimate fails, the swap would revert as built. Check the quote, the allowance and the deadline before you try again.
|
|
113
|
+
|
|
114
|
+
### Launch a token with Hook Blocks
|
|
79
115
|
|
|
80
|
-
|
|
116
|
+
One launch creates the token and opens its pools, each with its own Hook Blocks, frozen when the pool opens. `defaultRulesLaunch` sets every block to its default. Change what you want, and `rulesPoolTerms` checks every value against the contracts' bounds and returns the pool's caps and gas.
|
|
81
117
|
|
|
82
|
-
|
|
83
|
-
pool on the recapture root, or use the `kernel` a `readMarket` row carries.
|
|
118
|
+
Anti-Snipe's launch guard, on by default, counts the blocks the contracts read as `block.number`: Ethereum's, which each Robinhood Chain block carries as `l1BlockNumber`. `readParentBlockNumber` reads it. `getBlockNumber()` is the chain's own, far larger number, and a guard counted from it is one the Rules refuse.
|
|
84
119
|
|
|
85
120
|
```ts
|
|
86
|
-
import {
|
|
87
|
-
import {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
121
|
+
import { zeroHash } from "viem";
|
|
122
|
+
import {
|
|
123
|
+
ARTIFACT_ABIS,
|
|
124
|
+
HOOKR_1,
|
|
125
|
+
RECAPTURE_OFF,
|
|
126
|
+
ZERO,
|
|
127
|
+
buildLaunch,
|
|
128
|
+
defaultRulesLaunch,
|
|
129
|
+
launchFeeAbi,
|
|
130
|
+
readParentBlockNumber,
|
|
131
|
+
rulesAbi,
|
|
132
|
+
rulesPoolTerms,
|
|
133
|
+
type Admission,
|
|
134
|
+
} from "hookr-sdk";
|
|
135
|
+
|
|
136
|
+
const { HookrRegistry, HookrRoot, HookrRules, HookrLauncher } = HOOKR_1.contracts;
|
|
137
|
+
const blockNumber = await readParentBlockNumber(client);
|
|
138
|
+
const [open, admission, minProtocolShareBps, [launchFee]] = await Promise.all([
|
|
139
|
+
client.readContract({ address: HookrRegistry, abi: ARTIFACT_ABIS.HookrRegistry, functionName: "rootOpenFor", args: [HookrRoot, account] }),
|
|
140
|
+
client.readContract({ address: HookrRegistry, abi: ARTIFACT_ABIS.HookrRegistry, functionName: "admission", args: [HookrRoot, HookrRules] }),
|
|
141
|
+
client.readContract({ address: HookrRules, abi: rulesAbi, functionName: "minProtocolShareBps" }),
|
|
142
|
+
client.readContract({ address: HookrLauncher, abi: launchFeeAbi, functionName: "launchFee" }),
|
|
143
|
+
]);
|
|
144
|
+
if (!open) throw new Error("The root takes no launch from this wallet now");
|
|
145
|
+
|
|
146
|
+
// Your opening price and range. ETH is currency0, so amount0Max is the ETH you seed.
|
|
147
|
+
const seed = { sqrtPriceX96, tickLower, tickUpper, liquidity, amount0Max, amount1Max };
|
|
148
|
+
const ctx = {
|
|
149
|
+
blockNumber,
|
|
150
|
+
minProtocolShareBps,
|
|
151
|
+
launchLiquidity: seed.liquidity,
|
|
152
|
+
launchSqrtPriceX96: seed.sqrtPriceX96,
|
|
153
|
+
quoteIsCurrency0: true,
|
|
154
|
+
liquidityOwner: HookrLauncher,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const settings = defaultRulesLaunch(ctx);
|
|
158
|
+
settings.rules.burnBps = 50; // Auto Burn, in basis points
|
|
159
|
+
settings.rules.lpBps = 300; // LP Rewards, in basis points
|
|
160
|
+
const terms = rulesPoolTerms(settings, ctx, admission as Admission);
|
|
161
|
+
|
|
162
|
+
const member = {
|
|
163
|
+
quote: ZERO,
|
|
164
|
+
tickSpacing: settings.tickSpacing,
|
|
165
|
+
...seed,
|
|
166
|
+
config: {
|
|
167
|
+
subject: ZERO, // the launcher fills in the token
|
|
168
|
+
quote: ZERO,
|
|
169
|
+
rules: HookrRules,
|
|
170
|
+
advisory: ZERO,
|
|
171
|
+
liquidityOwner: HookrLauncher,
|
|
172
|
+
baseLpFeePips: settings.baseLpFeePips,
|
|
173
|
+
caps: terms.caps,
|
|
174
|
+
rulesGasLimit: terms.rulesGasLimit,
|
|
175
|
+
advisoryGasLimit: terms.advisoryGasLimit,
|
|
176
|
+
advisoryPhases: terms.advisoryPhases,
|
|
177
|
+
advisoryFailOpen: false,
|
|
178
|
+
policyId: zeroHash,
|
|
179
|
+
},
|
|
180
|
+
rules: settings.rules,
|
|
181
|
+
recapture: RECAPTURE_OFF,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const token = { existing: ZERO, name: "Hook", symbol: "HOOK", supply: 10n ** 27n, salt };
|
|
185
|
+
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);
|
|
186
|
+
const launchCall = buildLaunch(manifest, token, HookrRoot, [member], seed.amount0Max, deadline, launchFee);
|
|
187
|
+
await wallet.sendTransaction({ account, to: launchCall.to, data: launchCall.data, value: launchCall.value });
|
|
97
188
|
```
|
|
98
189
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
the
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
190
|
+
One launch opens one to eight pools of the same token, each on its own quote: pass more members. `buildLaunchAdvised` binds an advisory per pool, and `buildLaunchFamily` adds a dev buy, supply weights and an opening price check. `HookrLauncher.predictToken(creator, token)` gives the token's address before you send.
|
|
191
|
+
|
|
192
|
+
A member with arb recapture on takes the release's default `RecaptureConfig` in place of `RECAPTURE_OFF`. Read it with `recaptureAbi` at the HookrRules address: `recaptureDefaults()` belongs to HookrRecapture, which HookrRules reaches for every call it does not handle itself.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { recaptureAbi } from "hookr-sdk";
|
|
196
|
+
|
|
197
|
+
const [recapture] = await client.readContract({ address: HookrRules, abi: recaptureAbi, functionName: "recaptureDefaults" });
|
|
198
|
+
const recaptureMember = { ...member, recapture };
|
|
199
|
+
```
|
|
107
200
|
|
|
108
|
-
|
|
201
|
+
### Open pools for an existing token
|
|
109
202
|
|
|
110
|
-
|
|
111
|
-
root, so `listRoots` shows every kernel it has registered, current or not, with its status and
|
|
112
|
-
whether its profile is sealed (the coordinator opens markets on sealed profiles only):
|
|
203
|
+
The same launch, with the token named and nothing to mint. The launcher pulls the token side of each pool, so approve it first: `launchFunding` lists every allowance the launch needs.
|
|
113
204
|
|
|
114
205
|
```ts
|
|
115
|
-
import {
|
|
206
|
+
import { buildApprovals, launchFunding } from "hookr-sdk";
|
|
116
207
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
208
|
+
const existing = { existing: tokenAddress, name: "", symbol: "", supply: 0n, salt: zeroHash };
|
|
209
|
+
const funding = launchFunding(manifest, tokenAddress, true, [member], launchFee);
|
|
210
|
+
for (const allowance of funding.allowances)
|
|
211
|
+
for (const approve of buildApprovals(manifest, allowance, currentAllowance))
|
|
212
|
+
await wallet.sendTransaction({ account, to: approve.to, data: approve.data });
|
|
213
|
+
|
|
214
|
+
const openCall = buildLaunch(manifest, existing, HookrRoot, [member], seed.amount0Max, deadline, launchFee);
|
|
120
215
|
```
|
|
121
216
|
|
|
122
|
-
|
|
123
|
-
passed as `hooks`; only market creation is limited to `ROOTS`.
|
|
217
|
+
`HookrLauncher.memberKey(subject, quote, tickSpacing, root)` gives each pool's `PoolKey` and whether it is still free.
|
|
124
218
|
|
|
125
|
-
|
|
219
|
+
### Claim
|
|
220
|
+
|
|
221
|
+
Royalties, integrator fees and a trader's arb recapture rebate are held per currency in each pool's own Rules, the `config.rules` its `poolConfig` names (see [Read a pool](#read-a-pool)), until claimed.
|
|
126
222
|
|
|
127
223
|
```ts
|
|
128
|
-
import {
|
|
224
|
+
import { rulesAbi } from "hookr-sdk";
|
|
129
225
|
|
|
130
|
-
const owed = await client.readContract(
|
|
131
|
-
if (owed > 0n)
|
|
226
|
+
const owed = await client.readContract({ address: config.rules, abi: rulesAbi, functionName: "claimable", args: [currency, account] });
|
|
227
|
+
if (owed > 0n)
|
|
228
|
+
await wallet.writeContract({ account, address: config.rules, abi: rulesAbi, functionName: "claim", args: [currency] });
|
|
132
229
|
```
|
|
133
230
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
231
|
+
`claimTo` sends the claim to another address. `claimAsClaims` takes it as Uniswap v4 ERC-6909 claims.
|
|
232
|
+
|
|
233
|
+
## Addresses
|
|
234
|
+
|
|
235
|
+
Robinhood Chain, chain id 4663. `HOOKR_1.contracts` holds every address. `HOOKR_1.deployment` holds the deployment: the block the manifest starts from and its hash, each contract's runtime code hash, and the hash of the release's source verification on Blockscout. Each address below opens on RobinScan, where its source is verified.
|
|
236
|
+
|
|
237
|
+
| Contract | `hookr-1` | What it does |
|
|
238
|
+
| --- | --- | --- |
|
|
239
|
+
| HookrRoot | [`0x89c9FB50d1f03192230FFB1c279BB6E7d3Fe6aCc`](https://robin.etherscan.io/address/0x89c9FB50d1f03192230FFB1c279BB6E7d3Fe6aCc) | The hook each pool names in `PoolKey.hooks` |
|
|
240
|
+
| HookrRouter | [`0xC2Bc9a3fe6c4Ce8C2F7EB4655b0869b01D8c93C9`](https://robin.etherscan.io/address/0xC2Bc9a3fe6c4Ce8C2F7EB4655b0869b01D8c93C9) | Swaps through a Hookr pool |
|
|
241
|
+
| HookrQuoter | [`0x445D7891381Aa7F91F5048EdBC6a611ccBDe7865`](https://robin.etherscan.io/address/0x445D7891381Aa7F91F5048EdBC6a611ccBDe7865) | Quotes a swap with the pool's own callbacks |
|
|
242
|
+
| HookrLauncher | [`0x1cf2748A6670572425eAbf9Ab9e5BC4eb2eB8Ef4`](https://robin.etherscan.io/address/0x1cf2748A6670572425eAbf9Ab9e5BC4eb2eB8Ef4) | Opens a token's pools in one launch |
|
|
243
|
+
| HookrRules | [`0x4a71c270b1520F94064268102AebD744b0DE7942`](https://robin.etherscan.io/address/0x4a71c270b1520F94064268102AebD744b0DE7942) | Holds each pool's Hook Blocks and pays its fees |
|
|
244
|
+
| HookrRegistry | [`0x463c82beA1a52F4Cc05ca0741a24F0AD24bA4602`](https://robin.etherscan.io/address/0x463c82beA1a52F4Cc05ca0741a24F0AD24bA4602) | Admits roots, Hook Blocks, advisories and quotes, behind the owner's timelock |
|
|
245
|
+
| HookrTreasury | [`0x2242F7FC75606500503075f29ea8cDA684EfB4a9`](https://robin.etherscan.io/address/0x2242F7FC75606500503075f29ea8cDA684EfB4a9) | Holds Hookr's share and the integrator list |
|
|
246
|
+
| HookrForwarder | [`0xe7863CC0E2Fb783b44c279aB159bcD812c2ceA1c`](https://robin.etherscan.io/address/0xe7863CC0E2Fb783b44c279aB159bcD812c2ceA1c) | Runs a signed swap for a relayer |
|
|
247
|
+
| PoolManager | [`0x8366a39CC670B4001A1121B8F6A443A643e40951`](https://robin.etherscan.io/address/0x8366a39CC670B4001A1121B8F6A443A643e40951) | Uniswap v4's PoolManager, `POOL_MANAGER` |
|
|
248
|
+
|
|
249
|
+
Every other contract is in `HOOKR_1.contracts`.
|
|
250
|
+
|
|
251
|
+
### Rules modules
|
|
252
|
+
|
|
253
|
+
A pool binds one HookrRules, its `PoolConfig.rules`, and keeps it. Beside the release's own, the registry admits three more on `HookrRoot` on the same terms, each with the HookrRecapture its constructor created. `HOOKR_1.rulesModules` holds all four and `rulesModule(HOOKR_1, name)` names one. The manifest pins each by its runtime code hash, so `readReleaseManifest` refuses one that runs other code.
|
|
254
|
+
|
|
255
|
+
| Name | Rules | HookrRecapture | What a pool on them runs |
|
|
256
|
+
| --- | --- | --- | --- |
|
|
257
|
+
| `standard` | [`0x4a71c270b1520F94064268102AebD744b0DE7942`](https://robin.etherscan.io/address/0x4a71c270b1520F94064268102AebD744b0DE7942) | [`0xF83569E564d70daebc35950f66a73f2e2B915583`](https://robin.etherscan.io/address/0xF83569E564d70daebc35950f66a73f2e2B915583) | The release's HookrRules |
|
|
258
|
+
| `whole-pot` | [`0x10f684361b35C8411a2c07F68A6fB1F4B7e555F7`](https://robin.etherscan.io/address/0x10f684361b35C8411a2c07F68A6fB1F4B7e555F7) | [`0x60Efc4a41498e9433be2155b6cc1677AEC31D649`](https://robin.etherscan.io/address/0x60Efc4a41498e9433be2155b6cc1677AEC31D649) | The same Rules, where King of the Pool's winner takes the whole pot |
|
|
259
|
+
| `wallet-cap` | [`0xb67b73E28C522a3AAC438bE970bf94976Deee52a`](https://robin.etherscan.io/address/0xb67b73E28C522a3AAC438bE970bf94976Deee52a) | [`0xbbC43DC2f01F5E7882caCd78EF094A44eF0E4d23`](https://robin.etherscan.io/address/0xbbC43DC2f01F5E7882caCd78EF094A44eF0E4d23) | The whole-pot Rules plus [Wallet Cap](#wallet-cap) |
|
|
260
|
+
| `round-trip` | [`0x3d7e31746127FD9a14d3C7fC12E73611D2e8cb34`](https://robin.etherscan.io/address/0x3d7e31746127FD9a14d3C7fC12E73611D2e8cb34) | [`0xddFF6326E5CB9AA98b88e9064d9A9570f777f8A3`](https://robin.etherscan.io/address/0xddFF6326E5CB9AA98b88e9064d9A9570f777f8A3) | Rules that keep a record of who traded in each block, for Buy/Sell Block's round-trip check |
|
|
261
|
+
|
|
262
|
+
## Wallet Cap
|
|
263
|
+
|
|
264
|
+
While a launch's guard lasts, no wallet can buy more than the pool's cap of the token, its `rules.potMinBuyQuote`. A pool takes it at launch by binding the `wallet-cap` Rules.
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
import { checkWalletCapLaunch, readWalletCap, rulesModule, walletCapAmount, walletCapPoolTerms, walletCapRevert } from "hookr-sdk";
|
|
268
|
+
|
|
269
|
+
const walletCap = rulesModule(HOOKR_1, "wallet-cap");
|
|
270
|
+
const capAdmission = await client.readContract({ address: HookrRegistry, abi: ARTIFACT_ABIS.HookrRegistry, functionName: "admission", args: [HookrRoot, walletCap.rules] });
|
|
271
|
+
|
|
272
|
+
// The defaults keep the launch guard on: 50 parent blocks, about 10 minutes. Until it ends, no wallet buys more than 2% of the supply.
|
|
273
|
+
const capped = defaultRulesLaunch(ctx);
|
|
274
|
+
capped.rules.potMinBuyQuote = walletCapAmount(token.supply, 200);
|
|
275
|
+
const cappedTerms = walletCapPoolTerms(capped, ctx, capAdmission as Admission);
|
|
276
|
+
const cappedMember = {
|
|
277
|
+
...member,
|
|
278
|
+
config: { ...member.config, rules: walletCap.rules, caps: cappedTerms.caps, rulesGasLimit: cappedTerms.rulesGasLimit },
|
|
279
|
+
rules: capped.rules,
|
|
280
|
+
};
|
|
281
|
+
checkWalletCapLaunch([cappedMember]);
|
|
282
|
+
|
|
283
|
+
// A pool binds only while its guard has yet to end, so the launch must land well inside it.
|
|
284
|
+
const cappedDeadline = BigInt(Math.floor(Date.now() / 1000) + 300);
|
|
285
|
+
const cappedCall = buildLaunch(manifest, token, HookrRoot, [cappedMember], seed.amount0Max, cappedDeadline, launchFee);
|
|
286
|
+
|
|
287
|
+
// Once it trades: what a wallet may still buy, until the guard ends at a parent block (readParentBlockNumber).
|
|
288
|
+
const cap = await readWalletCap(client, { poolId: id, wallet: account, subject: tokenAddress });
|
|
289
|
+
if (cap) console.log(`${cap.room} of ${cap.cap} left until parent block ${cap.guardEndBlock}`);
|
|
290
|
+
|
|
291
|
+
// A buy past the cap fails its quote and its gas estimate with GuardWalletLimit, however the contracts wrapped it.
|
|
292
|
+
try {
|
|
293
|
+
await client.estimateGas({ account, to: swapCall.to, data: swapCall.data, value: swapCall.value });
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.log(walletCapRevert(error) ?? error);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`readWalletCap` reads the pool through the Rules' `extsload`, one storage word at a time, and gives null for a pool on any other Rules. `walletCapPoolSlot` and `walletCapBoughtSlot` give the slots for reads of your own, and `WALLET_CAP_ABI` is what the Wallet Cap Rules add to `rulesAbi`.
|
|
300
|
+
|
|
301
|
+
## Arb recapture
|
|
302
|
+
|
|
303
|
+
A pool with arb recapture on lets What The Hook's executor close the price gap between the pool and outside venues inside the trader's own swap, and pays the profit out:
|
|
304
|
+
|
|
305
|
+
| Who | Share of each profit |
|
|
306
|
+
| --- | --- |
|
|
307
|
+
| Hookr | 25% (`RECAPTURE_PROTOCOL_BPS`, 2,500) |
|
|
308
|
+
| What The Hook | up to 25% (`MAX_LANE_PARTNER_BPS`, 2,500), what its executor keeps |
|
|
309
|
+
| The pool | the rest: the trader's rebate (0 to 50% of it, 25% by default), the King of the Pool pot (off by default; up to 50% of it on the standard and round-trip Rules, up to all of it on the whole-pot and Wallet Cap Rules) and the LPs |
|
|
310
|
+
|
|
311
|
+
The pool's split is frozen at launch. `recaptureSplit` and `recaptureProfit` work it out to the wei; `recaptureSplit` takes a pot of up to 50%. The SDK's builders always open a pool with the pot off. The LPs' part is released over `releaseBlocks` L2 blocks: 1,200 to 864,000, 36,000 by default. The release counts at most 10 of them a second, so 36,000 blocks take at least an hour.
|
|
312
|
+
|
|
313
|
+
The lane's switch is not frozen: the registry's owner, or its guardian once one is appointed, can switch the lane off, or lower its gas cap, for every pool that froze it, at once, and the swaps then skip or shrink arb recapture. Switching it back on, or raising the cap, waits in the registry's 30-minute timelock. The switch touches nothing else of the pool.
|
|
314
|
+
|
|
315
|
+
## Gas
|
|
316
|
+
|
|
317
|
+
A swap on a pool with arb recapture on must carry the lane's gas floor, or it reverts `InsufficientLaneGas`. The floor is `2 × gasCap + gasCap / 63 + 755,000`: `laneGasFloor(gasCap)` here, `gasFloor` from `laneOf(id)` on chain. At the lane's 3,000,000 gas cap it is 6,802,619. The swap's own gas comes on top: a buy through `HookrRouter` on a live lane pool estimates at about 7.5 million.
|
|
318
|
+
|
|
319
|
+
Estimate and add a sixth, as the [Swap](#swap) example does. The estimate already includes the floor, which is why it is high. If your wallet sets a fixed limit, use at least 8.8 million on such a pool. A wallet pays for the gas used, not the limit. A pool without arb recapture has no floor.
|
|
320
|
+
|
|
321
|
+
## Quotes
|
|
322
|
+
|
|
323
|
+
A pool may quote in native ETH, any of the 22 tokens on the genesis catalog, any Robinhood stock token, and any other token the registry admits. `ROBINHOOD_QUOTE_CATALOG` is the catalog in the registry's order, each entry read from chain at block 81,225,166: address, symbol, decimals and runtime code hash. `classifyQuote` labels any address, `quoteWarnings` says what to tell a user, and `quoteAdmission` asks the registry whether a quote is admitted right now.
|
|
324
|
+
|
|
325
|
+
## What is in the package
|
|
326
|
+
|
|
327
|
+
| Source file | What it does |
|
|
328
|
+
| --- | --- |
|
|
329
|
+
| `releases.ts` | `HOOKR_1`: addresses, deployment, the Rules modules, the manifest, `ROBINHOOD_CHAIN`, explorer links, the lane gas floor and the parent block the launch guard counts |
|
|
330
|
+
| `wallet-cap.ts` | Wallet Cap: the cap from a share of supply, a pool's terms and a launch's checks, the reads through `extsload`, and its refusal in plain words |
|
|
331
|
+
| `wallet.ts` | The launcher, router and quoter calls, each checked against its bounds |
|
|
332
|
+
| `launch.ts` | Multi-pool launch, the dev buy and exactly what the launcher pulls |
|
|
333
|
+
| `params.ts` | Every setting of the core Hook Blocks and advisories, with its range and default, and the checks that refuse a value outside them |
|
|
334
|
+
| `rules.ts` | Encodes and checks a pool's Hook Blocks, and prices the Snipe tax, the dynamic fee and the arb recapture split |
|
|
335
|
+
| `quotes.ts` | The quote catalog, quote classes and amounts in each quote's own decimals |
|
|
336
|
+
| `registry.ts` | Typed registry operations and the admission checks |
|
|
337
|
+
| `advisories.ts` | The managed fee and market-hours advisory configs |
|
|
338
|
+
| `abi.ts` | The call ABIs for the root and its lane, the router, quoter, launcher, HookrRules, HookrRecapture, the registry, treasury, programs, referrals and forwarder |
|
|
339
|
+
| `generated.ts` | `ARTIFACT_ABIS`, the full ABIs of the registry, HookrRules, the treasury, the root factory and pair root, the fee and session advisories, the liquidity vault, the family contracts, LP Boost and the owned-root contracts, and the constants the encoders use |
|
|
340
|
+
| `vault.ts`, `referrals.ts`, `proofs.ts` | Liquidity vault and treasury calls, referral authorizations and reward proofs |
|
|
341
|
+
| `client.ts` | `HookrClient`, for a Hookr service at a URL you give it. Hookr runs no public service. |
|
|
342
|
+
|
|
343
|
+
Everything but `releases.ts`, `deployments.ts` and `wallet-cap.ts` is the release SDK hookr.fun runs, copied byte for byte. Its quote-admission exports in `registry.ts` are deprecated: HookrRegistry does not have their calls. `provenance.json` names the release's source commit and the hash of every file. Every contract of the release has its source verified at its address on RobinScan and Sourcify.
|
|
344
|
+
|
|
345
|
+
Before you send a call, check the wallet's chain, the payer, the token approvals, the deadline and the slippage yourself. A quote simulates the pool's accounting. It does not prove approvals, balances or gas. Treat token names and links as untrusted display text.
|
|
346
|
+
|
|
347
|
+
## Links
|
|
348
|
+
|
|
349
|
+
- Docs: [hookr.fun/docs](https://hookr.fun/docs)
|
|
350
|
+
- Explorer: [RobinScan](https://robin.etherscan.io)
|
|
351
|
+
- Moving from 0.2.x: [MIGRATING.md](https://unpkg.com/hookr-sdk@1/MIGRATING.md), in the package
|
|
352
|
+
- Changes: [CHANGELOG.md](https://unpkg.com/hookr-sdk@1/CHANGELOG.md), in the package
|
|
353
|
+
|
|
354
|
+
## License
|
|
355
|
+
|
|
356
|
+
MIT
|