@rev-net/core-v6 0.0.23 → 0.0.25
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/ADMINISTRATION.md +27 -0
- package/ARCHITECTURE.md +53 -129
- package/AUDIT_INSTRUCTIONS.md +108 -375
- package/CHANGELOG.md +65 -0
- package/README.md +76 -175
- package/RISKS.md +16 -4
- package/SKILLS.md +30 -391
- package/STYLE_GUIDE.md +59 -20
- package/USER_JOURNEYS.md +55 -478
- package/package.json +3 -3
- package/references/operations.md +311 -0
- package/references/runtime.md +98 -0
- package/script/Deploy.s.sol +12 -12
- package/src/REVLoans.sol +10 -10
- package/src/interfaces/IREVDeployer.sol +1 -1
- package/test/TestSplitWeightE2E.t.sol +10 -6
- package/test/TestSplitWeightFork.t.sol +10 -6
- package/test/fork/ForkTestBase.sol +10 -6
- package/CHANGE_LOG.md +0 -420
package/USER_JOURNEYS.md
CHANGED
|
@@ -1,508 +1,85 @@
|
|
|
1
|
-
# User Journeys
|
|
1
|
+
# User Journeys
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Who This Repo Serves
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- teams launching autonomous Revnets with encoded stage transitions
|
|
6
|
+
- participants buying, holding, and cashing out Revnet exposure over time
|
|
7
|
+
- borrowers using Revnet tokens as collateral instead of selling them
|
|
8
|
+
- operators working inside the narrow post-launch envelope the deployer allows
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
## Journey 1: Launch A Revnet With Its Long-Lived Rules Encoded Up Front
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
**Starting state:** you know the stage schedule, issuance behavior, optional integrations, and runtime controls the Revnet should allow.
|
|
10
13
|
|
|
11
|
-
**
|
|
14
|
+
**Success:** the Revnet launches as a Juicebox project whose deploy-time shape already encodes its economic envelope.
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
**Flow**
|
|
17
|
+
1. Use `REVDeployer` with the staged config, split operators, and optional integrations such as 721 hooks, buyback routing, router terminal support, or suckers.
|
|
18
|
+
2. The deployer launches the underlying project and keeps the ownership model aligned with the Revnet runtime contracts.
|
|
19
|
+
3. Stage config, issuance behavior, and auxiliary surfaces are committed as part of the launch instead of being left to human operators later.
|
|
20
|
+
4. The network can now accept payments and transition across stages without ordinary project-owner governance.
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
## Journey 2: Participate In The Revnet Across Stage Transitions
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|-----------|------|-------------|
|
|
19
|
-
| `revnetId` | `uint256` | Set to 0 to deploy a new revnet. |
|
|
20
|
-
| `configuration.description` | `REVDescription` | `name`, `ticker`, `uri`, `salt` for the ERC-20 token. |
|
|
21
|
-
| `configuration.baseCurrency` | `uint32` | 1 = ETH, 2 = USD. Determines the denomination for issuance weights. |
|
|
22
|
-
| `configuration.splitOperator` | `address` | The address that will manage splits, 721 tiers, and suckers. |
|
|
23
|
-
| `configuration.stageConfigurations` | `REVStageConfig[]` | One or more stages defining the revnet's economics. |
|
|
24
|
-
| `terminalConfigurations` | `JBTerminalConfig[]` | Which terminals and tokens the revnet accepts. |
|
|
25
|
-
| `suckerDeploymentConfiguration` | `REVSuckerDeploymentConfig` | Cross-chain sucker config. Set `salt = bytes32(0)` to skip. |
|
|
24
|
+
**Starting state:** the Revnet is live and a participant wants to pay in, hold exposure, or cash out later.
|
|
26
25
|
|
|
27
|
-
**
|
|
26
|
+
**Success:** participation follows the current stage's rules without requiring the user to reason about every deploy-time parameter directly.
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- Fund access limits: unlimited surplus allowance per terminal/token (for loans)
|
|
34
|
-
- Encoded configuration hash computed from economic parameters
|
|
35
|
-
- Auto-issuance amounts stored: `amountToAutoIssue[revnetId][block.timestamp + i][beneficiary] += count`
|
|
36
|
-
3. `CONTROLLER.launchProjectFor` creates the Juicebox project, minting the ERC-721 to REVDeployer
|
|
37
|
-
4. Cash-out delay set if first stage's `startsAtOrAfter` has already passed (existing revnet deploying to new chain)
|
|
38
|
-
5. `CONTROLLER.deployERC20For` deploys the project's ERC-20 token
|
|
39
|
-
6. Buyback pools initialized for each terminal token via `_tryInitializeBuybackPoolFor` (try-catch, silent failure OK)
|
|
40
|
-
7. Suckers deployed if `suckerDeploymentConfiguration.salt != bytes32(0)`
|
|
41
|
-
8. Config hash stored: `hashedEncodedConfigurationOf[revnetId] = encodedConfigurationHash`
|
|
42
|
-
9. **4-arg version only:** Default empty 721 hook deployed, split operator gets all 721 permissions
|
|
43
|
-
10. **6-arg version only:** Configured 721 hook deployed with prevention flags applied, croptop posts configured if any
|
|
28
|
+
**Flow**
|
|
29
|
+
1. Users pay through the configured terminal or router surface.
|
|
30
|
+
2. `REVOwner` enforces runtime behavior such as pay handling, cash-out rules, delayed exits, and other stage-sensitive constraints.
|
|
31
|
+
3. As stages advance, later pays and cash outs follow the newly active parameters while the project identity stays constant.
|
|
44
32
|
|
|
45
|
-
**
|
|
46
|
-
- `DeployRevnet(revnetId, configuration, terminalConfigurations, suckerDeploymentConfiguration, rulesetConfigurations, encodedConfigurationHash, caller)`
|
|
47
|
-
- `StoreAutoIssuanceAmount(revnetId, stageId, beneficiary, count, caller)` for each auto-issuance on this chain
|
|
48
|
-
- `DeploySuckers(...)` if suckers are deployed
|
|
49
|
-
- `SetCashOutDelay(revnetId, cashOutDelay, caller)` if applicable
|
|
33
|
+
**Failure cases that matter:** assuming a stage parameter is mutable when it is fixed at launch, misreading delayed cash-out behavior, and forgetting that optional integrations materially change the participant experience.
|
|
50
34
|
|
|
51
|
-
|
|
52
|
-
- If `startsAtOrAfter = 0` for the first stage, `block.timestamp` is used. The stage starts immediately.
|
|
53
|
-
- Auto-issuances with `chainId != block.chainid` are included in the config hash but not stored on this chain.
|
|
54
|
-
- Auto-issuances with `count = 0` are skipped (not stored, not included in config hash).
|
|
55
|
-
- Buyback pool initialization silently fails if the pool already exists.
|
|
56
|
-
- The `assert` on `launchProjectFor` return value catches project ID mismatches (should never happen).
|
|
35
|
+
## Journey 3: Claim Stage-Based Auto-Issuance When It Becomes Available
|
|
57
36
|
|
|
58
|
-
|
|
37
|
+
**Starting state:** the Revnet was deployed with auto-issuance allocations for one or more beneficiaries.
|
|
59
38
|
|
|
60
|
-
|
|
39
|
+
**Success:** the beneficiary claims the right amount for the right stage only after that stage is actually live.
|
|
61
40
|
|
|
62
|
-
**
|
|
41
|
+
**Flow**
|
|
42
|
+
1. Check `amountToAutoIssue(...)` for the revnet, stage, and beneficiary.
|
|
43
|
+
2. Call `autoIssueFor(...)` only once the target stage has started.
|
|
44
|
+
3. The stored allocation is consumed so the same stage allocation cannot be claimed twice.
|
|
63
45
|
|
|
64
|
-
|
|
65
|
-
- Caller must own the project's ERC-721 NFT
|
|
66
|
-
- Project must have no controller and no rulesets (blank project)
|
|
46
|
+
## Journey 4: Borrow Against Revnet Tokens Instead Of Selling Them
|
|
67
47
|
|
|
68
|
-
**
|
|
48
|
+
**Starting state:** a holder wants liquidity but does not want to exit the Revnet position.
|
|
69
49
|
|
|
70
|
-
|
|
71
|
-
2. Project NFT transferred from owner to REVDeployer via `safeTransferFrom` -- **irreversible**
|
|
72
|
-
3. `CONTROLLER.launchRulesetsFor` initializes rulesets for the existing project
|
|
73
|
-
4. `CONTROLLER.setUriOf` sets the project's metadata URI
|
|
74
|
-
5. Cash-out delay applied if first stage has already started
|
|
75
|
-
6. Same remaining steps as new deployment (ERC-20, buyback pools, suckers, 721 hook)
|
|
50
|
+
**Success:** the holder opens a loan, receives borrowed value, and keeps an NFT loan position representing the debt.
|
|
76
51
|
|
|
77
|
-
**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
52
|
+
**Flow**
|
|
53
|
+
1. The holder interacts with `REVLoans` using the eligible Revnet token exposure as collateral.
|
|
54
|
+
2. The system burns or escrows the relevant token exposure and mints a loan-position NFT.
|
|
55
|
+
3. Loan terms depend on the live Revnet economics rather than a static side system.
|
|
56
|
+
4. The borrower can later repay, transfer the loan NFT, or face liquidation if conditions require it.
|
|
82
57
|
|
|
83
|
-
|
|
84
|
-
- This is a **one-way operation**. The project NFT is permanently locked in REVDeployer.
|
|
85
|
-
- `launchRulesetsFor` reverts if rulesets already exist. `setControllerOf` reverts if a controller is already set.
|
|
86
|
-
- Useful in deploy scripts where the project ID is needed before configuration (e.g., for cross-chain sucker peer mappings).
|
|
58
|
+
## Journey 5: Repay, Transfer, Or Liquidate A Loan Position
|
|
87
59
|
|
|
88
|
-
|
|
60
|
+
**Starting state:** a loan already exists and either the borrower or another actor needs to change its state.
|
|
89
61
|
|
|
90
|
-
|
|
62
|
+
**Success:** the debt path settles cleanly and the collateral outcome matches the Revnet's current rules.
|
|
91
63
|
|
|
92
|
-
**
|
|
64
|
+
**Flow**
|
|
65
|
+
1. Repayment burns the debt and remints or releases the collateralized Revnet token position.
|
|
66
|
+
2. Transfers move the loan NFT, not the original collateralized exposure.
|
|
67
|
+
3. Liquidation consumes the loan under the rules encoded by `REVLoans` and the current Revnet state.
|
|
93
68
|
|
|
94
|
-
|
|
69
|
+
**Edge conditions that change user experience:** cross-ruleset loan behavior, zero-amount or zero-price edge cases, and sourced versus unsourced loan paths.
|
|
95
70
|
|
|
96
|
-
|
|
71
|
+
## Journey 6: Operate Inside The Bounded Post-Launch Control Envelope
|
|
97
72
|
|
|
98
|
-
|
|
99
|
-
2. Store calls `REVOwner.beforePayRecordedWith(context)`:
|
|
100
|
-
- Reads `tiered721HookOf` from REVOwner storage
|
|
101
|
-
- Calls 721 hook's `beforePayRecordedWith` for split specs (tier purchases)
|
|
102
|
-
- Computes `projectAmount = context.amount.value - totalSplitAmount`
|
|
103
|
-
- Calls buyback hook's `beforePayRecordedWith` with reduced amount context
|
|
104
|
-
- Scales weight: `weight = mulDiv(weight, projectAmount, context.amount.value)` (or 0 if `projectAmount == 0`)
|
|
105
|
-
- Returns merged hook specs: [721 hook spec, buyback hook spec]
|
|
106
|
-
3. Store calculates token count using the modified weight
|
|
107
|
-
4. Terminal mints tokens via controller
|
|
108
|
-
5. Terminal executes hook specs:
|
|
109
|
-
- 721 hook processes tier purchases
|
|
110
|
-
- Buyback hook processes swap (if applicable)
|
|
73
|
+
**Starting state:** the Revnet is live and an operator wants to use whatever ongoing controls the deployment allowed.
|
|
111
74
|
|
|
112
|
-
**
|
|
75
|
+
**Success:** the operator can use sanctioned controls without escaping the "autonomous after launch" model.
|
|
113
76
|
|
|
114
|
-
**
|
|
77
|
+
**Flow**
|
|
78
|
+
1. Review what `REVDeployer` allowed for split operators, stage evolution, and auxiliary integrations.
|
|
79
|
+
2. Use only those surfaces rather than treating the project like a normal owner-governed Juicebox project.
|
|
80
|
+
3. Audit cross-package behavior whenever the Revnet enabled buybacks, 721 hooks, router terminals, or suckers.
|
|
115
81
|
|
|
116
|
-
|
|
117
|
-
- If the buyback hook determines a DEX swap is better, weight = 0 and the buyback hook spec receives the full project amount. The buyback hook buys tokens on the DEX and mints them to the payer.
|
|
118
|
-
- If `totalSplitAmount >= context.amount.value`, `projectAmount = 0`, weight = 0, and no tokens are minted by the terminal. All funds go to 721 tier splits.
|
|
119
|
-
- If no 721 hook is set (`tiered721HookOf[revnetId] == address(0)` on REVOwner), only the buyback hook is consulted.
|
|
82
|
+
## Hand-Offs
|
|
120
83
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
## 4. Cash Out from a Revnet
|
|
124
|
-
|
|
125
|
-
**Entry point:** `JBMultiTerminal.cashOutTokensOf(holder, projectId, tokenCount, token, minTokensReclaimed, beneficiary, metadata)`
|
|
126
|
-
|
|
127
|
-
**What happens:**
|
|
128
|
-
|
|
129
|
-
1. Terminal records cash-out in store
|
|
130
|
-
2. Store calls `REVOwner.beforeCashOutRecordedWith(context)`:
|
|
131
|
-
- **If sucker:** Returns 0% tax, full cash-out count, no hooks (fee exempt)
|
|
132
|
-
- **If cash-out delay active:** Reads `cashOutDelayOf` from REVOwner storage, reverts with `REVDeployer_CashOutDelayNotFinished`
|
|
133
|
-
- **If no tax or no fee terminal:** Returns parameters unchanged
|
|
134
|
-
- **Otherwise:** Splits cash-out into fee portion (2.5%) and non-fee portion:
|
|
135
|
-
- `feeCashOutCount = mulDiv(cashOutCount, 25, 1000)`
|
|
136
|
-
- `nonFeeCashOutCount = cashOutCount - feeCashOutCount`
|
|
137
|
-
- Computes `postFeeReclaimedAmount` via bonding curve for non-fee tokens
|
|
138
|
-
- Computes `feeAmount` via bonding curve for fee tokens (on remaining surplus)
|
|
139
|
-
- Returns `nonFeeCashOutCount` as the adjusted cash-out count + hook spec for fee
|
|
140
|
-
3. Terminal burns ALL of the user's specified token count
|
|
141
|
-
4. Terminal transfers the reclaimed amount to the beneficiary
|
|
142
|
-
5. Terminal calls `REVOwner.afterCashOutRecordedWith(context)`:
|
|
143
|
-
- Transfers fee amount from terminal to this contract
|
|
144
|
-
- Pays fee to fee revnet's terminal via `feeTerminal.pay`
|
|
145
|
-
- On failure: returns funds to the originating project via `addToBalanceOf`
|
|
146
|
-
|
|
147
|
-
**Preview**: Call `JBMultiTerminal.previewCashOutFrom(holder, revnetId, cashOutCount, tokenToReclaim, beneficiary, metadata)` to simulate the full cash out including REVOwner's data hook effects (fee splitting, tax rate). Returns the expected reclaim amount and hook specifications. For a simpler estimate without data hook effects, use `JBTerminalStore.currentTotalReclaimableSurplusOf(revnetId, cashOutCount, decimals, currency)`.
|
|
148
|
-
|
|
149
|
-
**Events:** No revnet-specific events. Cash-out events are emitted by `JBMultiTerminal` and `JBController`. REVOwner's `beforeCashOutRecordedWith` is a `view` function. The `afterCashOutRecordedWith` hook on REVOwner processes fees but does not emit events.
|
|
150
|
-
|
|
151
|
-
**Edge cases:**
|
|
152
|
-
- Suckers bypass both the cash-out fee AND the cash-out delay. The `REVOwner._isSuckerOf` check is the only gate.
|
|
153
|
-
- `cashOutTaxRate == 0` means no tax and no revnet fee. The terminal's 2.5% protocol fee only applies up to the `feeFreeSurplusOf` amount (round-trip prevention), not the full reclaim.
|
|
154
|
-
- Micro cash-outs (< 40 wei at 2.5%) round `feeCashOutCount` to 0, bypassing the fee. Gas cost far exceeds the bypassed fee.
|
|
155
|
-
- The fee is paid to `FEE_REVNET_ID`, not `REV_ID`. These may be different projects.
|
|
156
|
-
- Both the revnet fee and the terminal protocol fee apply. The revnet fee is computed first (at the data hook level, by splitting the cashout token count into fee and non-fee portions), then the terminal's 2.5% protocol fee is applied to all outbound fund amounts (both the beneficiary's reclaim and the hook-forwarded fee amount).
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## 5. Borrow Against Revnet Tokens (REVLoans)
|
|
161
|
-
|
|
162
|
-
**Entry point:** `REVLoans.borrowFrom(revnetId, source, minBorrowAmount, collateralCount, beneficiary, prepaidFeePercent)`
|
|
163
|
-
|
|
164
|
-
**Prerequisites:**
|
|
165
|
-
- Caller must hold `collateralCount` revnet ERC-20 tokens
|
|
166
|
-
- Caller must grant `BURN_TOKENS` permission to the REVLoans contract for the revnet's project ID via `JBPermissions.setPermissionsFor()`. Without this, the transaction reverts in `JBController.burnTokensOf` with `JBPermissioned_Unauthorized`.
|
|
167
|
-
- The revnet's cash-out delay must have passed (if one was set during cross-chain deployment). `borrowableAmountFrom` returns 0 and `borrowFrom` reverts with `REVLoans_CashOutDelayNotFinished` until the 30-day delay expires.
|
|
168
|
-
|
|
169
|
-
**Key parameters:**
|
|
170
|
-
|
|
171
|
-
| Parameter | Type | Description |
|
|
172
|
-
|-----------|------|-------------|
|
|
173
|
-
| `revnetId` | `uint256` | The revnet to borrow from. |
|
|
174
|
-
| `source` | `REVLoanSource` | `{token, terminal}` -- which terminal and token to borrow. |
|
|
175
|
-
| `minBorrowAmount` | `uint256` | Slippage protection -- revert if you'd get less. |
|
|
176
|
-
| `collateralCount` | `uint256` | Number of revnet tokens to burn as collateral. |
|
|
177
|
-
| `beneficiary` | `address payable` | Receives the borrowed funds and fee payment tokens. |
|
|
178
|
-
| `prepaidFeePercent` | `uint256` | 25-500 (2.5%-50% of MAX_FEE=1000). Higher = longer interest-free window. |
|
|
179
|
-
|
|
180
|
-
**What happens:**
|
|
181
|
-
|
|
182
|
-
1. **Validation:**
|
|
183
|
-
- `collateralCount > 0` (no zero-collateral loans)
|
|
184
|
-
- `source.terminal` is registered for the revnet in the directory
|
|
185
|
-
- `prepaidFeePercent` in range [25, 500]
|
|
186
|
-
- Cash-out delay has passed: resolves the `REVOwner` from the current ruleset's `dataHook`, checks `IREVOwner.cashOutDelayOf(revnetId)` (stored on REVOwner). Reverts with `REVLoans_CashOutDelayNotFinished(cashOutDelay, block.timestamp)` if `cashOutDelay > block.timestamp`.
|
|
187
|
-
2. **Loan ID generation:** `revnetId * 1_000_000_000_000 + (++totalLoansBorrowedFor[revnetId])`
|
|
188
|
-
3. **Loan creation in storage:**
|
|
189
|
-
- `source`, `createdAt = block.timestamp`, `prepaidFeePercent`, `prepaidDuration = mulDiv(prepaidFeePercent, 3650 days, 500)`
|
|
190
|
-
4. **Borrow amount calculation:**
|
|
191
|
-
- `totalSurplus` from all terminals (aggregated via `JBSurplus.currentSurplusOf`)
|
|
192
|
-
- `totalBorrowed` from all loan sources (aggregated via `_totalBorrowedFrom`)
|
|
193
|
-
- `borrowAmount = JBCashOuts.cashOutFrom(surplus + borrowed, collateral, supply + totalCollateral, cashOutTaxRate)`
|
|
194
|
-
5. **Validation:** `borrowAmount > 0`, `borrowAmount >= minBorrowAmount`
|
|
195
|
-
6. **Source fee:** `JBFees.feeAmountFrom(borrowAmount, prepaidFeePercent)`
|
|
196
|
-
7. **`_adjust` executes:**
|
|
197
|
-
- Writes `loan.amount = borrowAmount` and `loan.collateral = collateralCount` to storage (CEI)
|
|
198
|
-
- `_addTo`:
|
|
199
|
-
- Registers the source if first time
|
|
200
|
-
- Increments `totalBorrowedFrom`
|
|
201
|
-
- Calls `terminal.useAllowanceOf` to pull funds (incurs 2.5% protocol fee automatically)
|
|
202
|
-
- Pays REV fee (1%) to `REV_ID` via `feeTerminal.pay` (try-catch; zeroed on failure)
|
|
203
|
-
- Transfers remaining: `netAmountPaidOut - revFeeAmount - sourceFeeAmount` to beneficiary
|
|
204
|
-
- `_addCollateralTo`: increments `totalCollateralOf`, burns collateral via `CONTROLLER.burnTokensOf`
|
|
205
|
-
- Pays source fee to revnet via `terminal.pay` (try-catch — on failure, returns fee amount to beneficiary)
|
|
206
|
-
8. **Mint loan ERC-721** to `_msgSender()`
|
|
207
|
-
|
|
208
|
-
**Events:** `Borrow(loanId, revnetId, loan, source, borrowAmount, collateralCount, sourceFeeAmount, beneficiary, caller)`
|
|
209
|
-
|
|
210
|
-
**Edge cases:**
|
|
211
|
-
- Revnets always deploy an ERC-20 at creation, so collateral is always ERC-20 tokens (never credits).
|
|
212
|
-
- The `minBorrowAmount` check is against the raw bonding curve output, BEFORE fees are deducted. The actual amount received is less.
|
|
213
|
-
- `prepaidDuration` at minimum (25): `25 * 3650 days / 500 = 182.5 days`. At maximum (500): `500 * 3650 days / 500 = 3650 days`.
|
|
214
|
-
- Both the REV fee payment and the source fee payment failures are non-fatal. If either `feeTerminal.pay` or `source.terminal.pay` reverts, the fee amount is transferred to the beneficiary instead.
|
|
215
|
-
- Loan NFT is minted to `_msgSender()`, not `beneficiary`. The caller owns the loan; the beneficiary receives the funds.
|
|
216
|
-
- When a revnet deploys to a new chain with `startsAtOrAfter` in the past, `REVDeployer` sets a 30-day cash-out delay via `REVOwner.setCashOutDelayOf()`. Both `borrowFrom` and `borrowableAmountFrom` enforce this delay by resolving the REVOwner from the current ruleset's `dataHook` and checking `IREVOwner.cashOutDelayOf(revnetId)` (stored on REVOwner). This prevents cross-chain arbitrage via loans during the delay window.
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
## 6. Repay a Loan
|
|
221
|
-
|
|
222
|
-
**Entry point:** `REVLoans.repayLoan(loanId, maxRepayBorrowAmount, collateralCountToReturn, beneficiary, allowance)`
|
|
223
|
-
|
|
224
|
-
**Key parameters:**
|
|
225
|
-
|
|
226
|
-
| Parameter | Type | Description |
|
|
227
|
-
|-----------|------|-------------|
|
|
228
|
-
| `loanId` | `uint256` | The loan to repay (from the ERC-721). |
|
|
229
|
-
| `maxRepayBorrowAmount` | `uint256` | Maximum amount willing to pay. Use `type(uint256).max` for "whatever it costs." |
|
|
230
|
-
| `collateralCountToReturn` | `uint256` | How many collateral tokens to get back (up to `loan.collateral`). |
|
|
231
|
-
| `beneficiary` | `address payable` | Receives the re-minted collateral tokens and fee payment tokens. |
|
|
232
|
-
| `allowance` | `JBSingleAllowance` | Optional permit2 data. Set `amount = 0` to skip. |
|
|
233
|
-
|
|
234
|
-
**What happens:**
|
|
235
|
-
|
|
236
|
-
1. **Authorization:** `_ownerOf(loanId) == _msgSender()` (only loan NFT owner can repay)
|
|
237
|
-
2. **Collateral check:** `collateralCountToReturn <= loan.collateral`
|
|
238
|
-
3. **Calculate new borrow amount** for remaining collateral via bonding curve:
|
|
239
|
-
- `newBorrowAmount = _borrowAmountFrom(loan, revnetId, loan.collateral - collateralCountToReturn)`
|
|
240
|
-
- Verify `newBorrowAmount <= loan.amount` (collateral value hasn't increased enough to over-collateralize)
|
|
241
|
-
- `repayBorrowAmount = loan.amount - newBorrowAmount`
|
|
242
|
-
4. **Nothing-to-do check:** Reverts if `repayBorrowAmount == 0 && collateralCountToReturn == 0`
|
|
243
|
-
5. **Source fee calculation:**
|
|
244
|
-
- If within prepaid window (`timeSinceCreated <= prepaidDuration`): fee = 0
|
|
245
|
-
- If expired (`timeSinceCreated > LOAN_LIQUIDATION_DURATION`): revert `REVLoans_LoanExpired`
|
|
246
|
-
- Otherwise: linear fee based on time elapsed beyond prepaid window
|
|
247
|
-
- `repayBorrowAmount += sourceFeeAmount` (fee added to repayment)
|
|
248
|
-
6. **Accept funds:** `_acceptFundsFor` handles native token (uses `msg.value`) or ERC-20 (with optional permit2)
|
|
249
|
-
7. **Max repay check:** `repayBorrowAmount <= maxRepayBorrowAmount`
|
|
250
|
-
8. **Execute repay** via `_repayLoan`:
|
|
251
|
-
- **Full repay** (`collateralCountToReturn == loan.collateral`): burns original NFT, calls `_adjust` with amounts zeroed, deletes loan, returns same loan ID
|
|
252
|
-
- **Partial repay:** burns original NFT, creates new loan with reduced amount/collateral, copies `createdAt`/`prepaidFeePercent`/`prepaidDuration` from original, mints new NFT
|
|
253
|
-
9. **Refund excess:** If `maxRepayBorrowAmount > repayBorrowAmount`, transfers the difference back to `_msgSender()`
|
|
254
|
-
|
|
255
|
-
**Events:** `RepayLoan(loanId, revnetId, paidOffLoanId, loan, paidOffLoan, repayBorrowAmount, sourceFeeAmount, collateralCountToReturn, beneficiary, caller)`
|
|
256
|
-
|
|
257
|
-
**Edge cases:**
|
|
258
|
-
- The source fee on repay is time-proportional. At the boundary of the prepaid window, it jumps from 0 to a small amount.
|
|
259
|
-
- Partial repay creates a new loan NFT with a new loan ID but preserves `createdAt` and `prepaidFeePercent`. The prepaid window clock doesn't reset.
|
|
260
|
-
- If the collateral has increased in value since borrowing (surplus grew), `newBorrowAmount > loan.amount` triggers `REVLoans_NewBorrowAmountGreaterThanLoanAmount`. The borrower should use `reallocateCollateralFromLoan` instead.
|
|
261
|
-
- For ERC-20 repayments, the contract tries standard `transferFrom` first. If allowance is insufficient, it falls through to permit2.
|
|
262
|
-
- `msg.value` is used directly for native token repayments (overrides `maxRepayBorrowAmount`).
|
|
263
|
-
|
|
264
|
-
---
|
|
265
|
-
|
|
266
|
-
## 7. Get Liquidated
|
|
267
|
-
|
|
268
|
-
**Entry point:** `REVLoans.liquidateExpiredLoansFrom(revnetId, startingLoanId, count)`
|
|
269
|
-
|
|
270
|
-
**Permissionless.** Anyone can call this to clean up expired loans.
|
|
271
|
-
|
|
272
|
-
**Key parameters:**
|
|
273
|
-
|
|
274
|
-
| Parameter | Type | Description |
|
|
275
|
-
|-----------|------|-------------|
|
|
276
|
-
| `revnetId` | `uint256` | The revnet to liquidate loans from. |
|
|
277
|
-
| `startingLoanId` | `uint256` | The LOAN NUMBER (not full loan ID) to start iterating from. |
|
|
278
|
-
| `count` | `uint256` | How many loan numbers to iterate over. |
|
|
279
|
-
|
|
280
|
-
**What happens (per loan in range):**
|
|
281
|
-
|
|
282
|
-
1. Construct full loan ID: `revnetId * 1_000_000_000_000 + (startingLoanId + i)`
|
|
283
|
-
2. Read loan from storage. If `createdAt == 0`, skip (already repaid or liquidated).
|
|
284
|
-
3. Check ownership. If `_ownerOf(loanId) == address(0)`, skip (already burned).
|
|
285
|
-
4. Check expiry: `block.timestamp > loan.createdAt + LOAN_LIQUIDATION_DURATION` (strictly greater than)
|
|
286
|
-
5. Burn the loan NFT via `_burn(loanId)`
|
|
287
|
-
6. Delete loan data: `delete _loanOf[loanId]`
|
|
288
|
-
7. Decrement `totalCollateralOf[revnetId]` by `loan.collateral`
|
|
289
|
-
8. Decrement `totalBorrowedFrom[revnetId][terminal][token]` by `loan.amount`
|
|
290
|
-
|
|
291
|
-
**Events:** `Liquidate(loanId, revnetId, loan, caller)` for each liquidated loan
|
|
292
|
-
|
|
293
|
-
**Edge cases:**
|
|
294
|
-
- The collateral was burned when the loan was created. There is nothing to "seize" -- liquidation is purely bookkeeping cleanup.
|
|
295
|
-
- The borrower retains whatever funds they borrowed. The burned collateral tokens are permanently lost.
|
|
296
|
-
- `startingLoanId` is the loan NUMBER within the revnet, not the full loan ID. The function constructs full IDs internally.
|
|
297
|
-
- Gaps in the loan ID sequence (from repaid or already-liquidated loans) are skipped via the `createdAt == 0` check.
|
|
298
|
-
- Gas cost scales linearly with `count`. Choose parameters carefully to avoid iterating over many empty slots.
|
|
299
|
-
- The `>` comparison means a loan is liquidatable starting at `createdAt + LOAN_LIQUIDATION_DURATION + 1` second.
|
|
300
|
-
|
|
301
|
-
---
|
|
302
|
-
|
|
303
|
-
## 8. Reallocate Collateral Between Loans
|
|
304
|
-
|
|
305
|
-
**Entry point:** `REVLoans.reallocateCollateralFromLoan(loanId, collateralCountToTransfer, source, minBorrowAmount, collateralCountToAdd, beneficiary, prepaidFeePercent)`
|
|
306
|
-
|
|
307
|
-
**Purpose:** If a loan's collateral has appreciated (surplus grew, or tax rate decreased), extract excess collateral and use it to open a new loan.
|
|
308
|
-
|
|
309
|
-
**Key parameters:**
|
|
310
|
-
|
|
311
|
-
| Parameter | Type | Description |
|
|
312
|
-
|-----------|------|-------------|
|
|
313
|
-
| `loanId` | `uint256` | The existing loan to take collateral from. |
|
|
314
|
-
| `collateralCountToTransfer` | `uint256` | Tokens to move from existing loan to new loan. |
|
|
315
|
-
| `source` | `REVLoanSource` | Must match the existing loan's source (same terminal + token). |
|
|
316
|
-
| `minBorrowAmount` | `uint256` | Slippage protection for the new loan. |
|
|
317
|
-
| `collateralCountToAdd` | `uint256` | Additional fresh tokens to add to the new loan (from caller's balance). |
|
|
318
|
-
| `beneficiary` | `address payable` | Receives proceeds from the new loan. |
|
|
319
|
-
| `prepaidFeePercent` | `uint256` | For the new loan (25-500). |
|
|
320
|
-
|
|
321
|
-
**What happens:**
|
|
322
|
-
|
|
323
|
-
1. **Authorization:** `_ownerOf(loanId) == _msgSender()`
|
|
324
|
-
2. **Expiry check:** If the loan has expired (`block.timestamp - createdAt > LOAN_LIQUIDATION_DURATION`), reverts with `REVLoans_LoanExpired`. Expired loans can only be liquidated, not reallocated.
|
|
325
|
-
3. **Source match:** New loan source must match existing loan source (prevents cross-source value extraction)
|
|
326
|
-
4. **`_reallocateCollateralFromLoan`:**
|
|
327
|
-
- Burns original loan NFT
|
|
328
|
-
- Validates `collateralCountToTransfer <= loan.collateral`
|
|
329
|
-
- Computes `newCollateralCount = loan.collateral - collateralCountToTransfer`
|
|
330
|
-
- Computes `borrowAmount = _borrowAmountFrom(loan, revnetId, newCollateralCount)`
|
|
331
|
-
- Validates `borrowAmount >= loan.amount` (remaining collateral must still cover the original loan amount)
|
|
332
|
-
- Creates replacement loan with original values
|
|
333
|
-
- Calls `_adjust` to reduce collateral (returns excess tokens to caller via `_returnCollateralFrom`)
|
|
334
|
-
- Mints replacement loan NFT to caller
|
|
335
|
-
- Deletes original loan data
|
|
336
|
-
5. **`borrowFrom`:** Opens a new loan with `collateralCountToTransfer + collateralCountToAdd` as collateral
|
|
337
|
-
- The `collateralCountToTransfer` tokens were just re-minted to the caller in step 4
|
|
338
|
-
- The `collateralCountToAdd` tokens come from the caller's existing balance
|
|
339
|
-
- Both are burned as collateral for the new loan
|
|
340
|
-
|
|
341
|
-
**Events:**
|
|
342
|
-
- `ReallocateCollateral(loanId, revnetId, reallocatedLoanId, reallocatedLoan, removedCollateralCount, caller)`
|
|
343
|
-
- `Borrow(newLoanId, revnetId, ...)` from the `borrowFrom` call
|
|
344
|
-
|
|
345
|
-
**Edge cases:**
|
|
346
|
-
- This function is NOT payable. Any ETH sent with the call is rejected at the EVM level.
|
|
347
|
-
- The original loan's `createdAt` and `prepaidFeePercent` are preserved on the replacement loan. The new loan gets fresh values.
|
|
348
|
-
- If `collateralCountToTransfer == loan.collateral`, the replacement loan has 0 collateral and must have `borrowAmount >= loan.amount` (which requires the bonding curve to return 0 for 0 collateral, which it does). But `borrowAmount = 0 < loan.amount` would revert. So you can't transfer ALL collateral -- some must remain.
|
|
349
|
-
- Between `_reallocateCollateralFromLoan` and `borrowFrom`, collateral tokens are minted to the caller and then immediately burned. If the caller is a contract with a `receive` function that re-enters, verify this is safe.
|
|
350
|
-
|
|
351
|
-
---
|
|
352
|
-
|
|
353
|
-
## 9. Claim Auto-Issuance
|
|
354
|
-
|
|
355
|
-
**Entry point:** `REVDeployer.autoIssueFor(revnetId, stageId, beneficiary)`
|
|
356
|
-
|
|
357
|
-
**Permissionless.** Anyone can call this on behalf of any beneficiary.
|
|
358
|
-
|
|
359
|
-
**Key parameters:**
|
|
360
|
-
|
|
361
|
-
| Parameter | Type | Description |
|
|
362
|
-
|-----------|------|-------------|
|
|
363
|
-
| `revnetId` | `uint256` | The revnet to claim from. |
|
|
364
|
-
| `stageId` | `uint256` | The stage ID (= `block.timestamp + stageIndex` from deployment). |
|
|
365
|
-
| `beneficiary` | `address` | The address to receive the tokens. |
|
|
366
|
-
|
|
367
|
-
**What happens:**
|
|
368
|
-
|
|
369
|
-
1. `CONTROLLER.getRulesetOf(revnetId, stageId)` retrieves the ruleset
|
|
370
|
-
2. Validates `ruleset.start <= block.timestamp` (stage has started)
|
|
371
|
-
3. Reads `count = amountToAutoIssue[revnetId][stageId][beneficiary]`
|
|
372
|
-
4. Validates `count > 0`
|
|
373
|
-
5. **Zeroes the amount BEFORE minting** (CEI pattern): `amountToAutoIssue[...] = 0`
|
|
374
|
-
6. `CONTROLLER.mintTokensOf(revnetId, count, beneficiary, "", useReservedPercent=false)`
|
|
375
|
-
|
|
376
|
-
**Events:** `AutoIssue(revnetId, stageId, beneficiary, count, caller)`
|
|
377
|
-
|
|
378
|
-
**Edge cases:**
|
|
379
|
-
- `useReservedPercent = false` means the FULL `count` goes to the beneficiary. No split percent is applied.
|
|
380
|
-
- Auto-issuance is one-time per (revnetId, stageId, beneficiary). Once claimed, `amountToAutoIssue` is zero and calling again reverts with `REVDeployer_NothingToAutoIssue`.
|
|
381
|
-
- The `stageId` must exactly match the value stored during deployment. If the deployment timestamp assumption doesn't hold (see RISKS.md S-3), the stage ID may be wrong and the claim will fail.
|
|
382
|
-
- Auto-issuances for other chains (where `chainId != block.chainid`) are not stored on this chain and cannot be claimed here.
|
|
383
|
-
|
|
384
|
-
---
|
|
385
|
-
|
|
386
|
-
## 10. Burn Held Tokens
|
|
387
|
-
|
|
388
|
-
**Entry point:** `REVDeployer.burnHeldTokensOf(revnetId)`
|
|
389
|
-
|
|
390
|
-
**Permissionless.** Anyone can call this.
|
|
391
|
-
|
|
392
|
-
**Purpose:** When reserved token splits don't sum to 100%, the remainder goes to the project owner (REVDeployer). This function burns those tokens to prevent them from sitting idle.
|
|
393
|
-
|
|
394
|
-
**What happens:**
|
|
395
|
-
|
|
396
|
-
1. Reads REVDeployer's token balance for the revnet
|
|
397
|
-
2. Reverts with `REVDeployer_NothingToBurn` if balance is 0
|
|
398
|
-
3. Burns all held tokens via `CONTROLLER.burnTokensOf`
|
|
399
|
-
|
|
400
|
-
**Events:** `BurnHeldTokens(revnetId, count, caller)`
|
|
401
|
-
|
|
402
|
-
---
|
|
403
|
-
|
|
404
|
-
## 11. Change Split Operator
|
|
405
|
-
|
|
406
|
-
**Entry point:** `REVDeployer.setSplitOperatorOf(revnetId, newSplitOperator)`
|
|
407
|
-
|
|
408
|
-
**Authorization:** Only the current split operator can call this.
|
|
409
|
-
|
|
410
|
-
**What happens:**
|
|
411
|
-
|
|
412
|
-
1. `_checkIfIsSplitOperatorOf(revnetId, _msgSender())` verifies caller is current operator
|
|
413
|
-
2. Revokes all permissions from old operator: `_setPermissionsFor(this, _msgSender(), revnetId, empty)`
|
|
414
|
-
3. Grants all split operator permissions to new operator: `_setSplitOperatorOf(revnetId, newSplitOperator)`
|
|
415
|
-
|
|
416
|
-
**Default permissions (9):** SET_SPLIT_GROUPS, SET_BUYBACK_POOL, SET_BUYBACK_TWAP, SET_PROJECT_URI, ADD_PRICE_FEED, SUCKER_SAFETY, SET_BUYBACK_HOOK, SET_ROUTER_TERMINAL, SET_TOKEN_METADATA
|
|
417
|
-
|
|
418
|
-
**Additional permissions (if not prevented by 721 hook deployment flags):** ADJUST_721_TIERS, SET_721_METADATA, MINT_721, SET_721_DISCOUNT_PERCENT
|
|
419
|
-
|
|
420
|
-
**Events:** `ReplaceSplitOperator(revnetId, newSplitOperator, caller)`
|
|
421
|
-
|
|
422
|
-
**Edge cases:**
|
|
423
|
-
- The split operator is singular. There can only be one at a time.
|
|
424
|
-
- Setting `newSplitOperator = address(0)` effectively abandons the operator role. Nobody can change splits after that.
|
|
425
|
-
- The old operator's permissions are fully revoked (set to empty array), not just the split-specific ones.
|
|
426
|
-
|
|
427
|
-
---
|
|
428
|
-
|
|
429
|
-
## 12. Deploy Suckers for an Existing Revnet
|
|
430
|
-
|
|
431
|
-
**Entry point:** `REVDeployer.deploySuckersFor(revnetId, suckerDeploymentConfiguration)`
|
|
432
|
-
|
|
433
|
-
**Authorization:** Only the split operator can call this. The current stage must allow sucker deployment (bit 2 of `extraMetadata`).
|
|
434
|
-
|
|
435
|
-
**What happens:**
|
|
436
|
-
|
|
437
|
-
1. `_checkIfIsSplitOperatorOf(revnetId, _msgSender())`
|
|
438
|
-
2. Reads current ruleset metadata
|
|
439
|
-
3. Checks `(metadata.metadata >> 2) & 1 == 1` (third bit = allow suckers)
|
|
440
|
-
4. Deploys suckers via `SUCKER_REGISTRY.deploySuckersFor` using stored config hash
|
|
441
|
-
|
|
442
|
-
**Events:** `DeploySuckers(revnetId, encodedConfigurationHash, suckerDeploymentConfiguration, caller)`
|
|
443
|
-
|
|
444
|
-
**Edge cases:**
|
|
445
|
-
- The `extraMetadata` bit check means sucker deployment can be disabled for specific stages. If the current stage doesn't allow it, the transaction reverts.
|
|
446
|
-
- The `encodedConfigurationHash` used for the salt comes from the stored hash, not a newly computed one. This ensures cross-chain consistency.
|
|
447
|
-
|
|
448
|
-
---
|
|
449
|
-
|
|
450
|
-
## 13. Stage Transitions (Automatic)
|
|
451
|
-
|
|
452
|
-
**No entry point.** Stage transitions happen automatically via the Juicebox rulesets system.
|
|
453
|
-
|
|
454
|
-
**How it works:**
|
|
455
|
-
|
|
456
|
-
Each stage is a JBRuleset with:
|
|
457
|
-
- `duration = issuanceCutFrequency` (e.g., 30 days)
|
|
458
|
-
- `weight = initialIssuance`
|
|
459
|
-
- `weightCutPercent = issuanceCutPercent`
|
|
460
|
-
- `mustStartAtOrAfter = startsAtOrAfter`
|
|
461
|
-
|
|
462
|
-
When a stage's duration expires, it either:
|
|
463
|
-
1. **Cycles:** If no later stage is ready to start, the current stage repeats with decayed weight (`weight *= (1 - weightCutPercent/1e9)`)
|
|
464
|
-
2. **Transitions:** If the next stage's `startsAtOrAfter` has been reached, the next stage activates with its own `initialIssuance`
|
|
465
|
-
|
|
466
|
-
**Impact on active loans:**
|
|
467
|
-
|
|
468
|
-
When a stage transition changes `cashOutTaxRate`:
|
|
469
|
-
- `_borrowableAmountFrom` uses the CURRENT stage's `cashOutTaxRate`
|
|
470
|
-
- A higher tax rate reduces borrowable amount per unit of collateral
|
|
471
|
-
- A lower tax rate increases borrowable amount per unit of collateral
|
|
472
|
-
- Existing loans are NOT automatically adjusted -- they retain their original `amount`
|
|
473
|
-
- A loan can become under-collateralized if the new tax rate is higher (collateral's cash-out value drops below `loan.amount`)
|
|
474
|
-
- The protocol has no mechanism to force repayment -- only the 10-year expiry applies
|
|
475
|
-
|
|
476
|
-
**Impact on payments:**
|
|
477
|
-
- New payments use the new stage's issuance rate
|
|
478
|
-
- The buyback hook compares DEX price vs. new issuance rate for swap-vs-mint decisions
|
|
479
|
-
|
|
480
|
-
**Impact on cash-outs:**
|
|
481
|
-
- The bonding curve uses the new stage's `cashOutTaxRate`
|
|
482
|
-
- If the new stage has a higher tax rate, cash-outs return less
|
|
483
|
-
- If the new stage has a lower tax rate, cash-outs return more
|
|
484
|
-
|
|
485
|
-
**Edge cases:**
|
|
486
|
-
- If `issuanceCutFrequency = 0` (no duration), the stage never expires. It must be replaced by a later stage's `startsAtOrAfter`.
|
|
487
|
-
- Weight decay across many cycles (20,000+) requires progressive cache updates via `updateRulesetWeightCache()`. Without caching, operations revert with `WeightCacheRequired`.
|
|
488
|
-
- The issuance cut is applied per cycle. After N cycles: `weight = initialIssuance * (1 - issuanceCutPercent/1e9)^N`.
|
|
489
|
-
|
|
490
|
-
---
|
|
491
|
-
|
|
492
|
-
## Summary: Entry Points by Actor
|
|
493
|
-
|
|
494
|
-
| Actor | Function | Authorization |
|
|
495
|
-
|-------|----------|---------------|
|
|
496
|
-
| Anyone | `REVDeployer.deployFor(0, ...)` | None (deploys new revnet) |
|
|
497
|
-
| Project owner | `REVDeployer.deployFor(existingId, ...)` | Must own project NFT |
|
|
498
|
-
| Anyone | `JBMultiTerminal.pay(...)` | None |
|
|
499
|
-
| Token holder | `JBMultiTerminal.cashOutTokensOf(...)` | Must hold tokens |
|
|
500
|
-
| Token holder | `REVLoans.borrowFrom(...)` | Must hold tokens + grant `BURN_TOKENS` permission to REVLoans |
|
|
501
|
-
| Loan owner | `REVLoans.repayLoan(...)` | Must own loan NFT |
|
|
502
|
-
| Loan owner | `REVLoans.reallocateCollateralFromLoan(...)` | Must own loan NFT |
|
|
503
|
-
| Anyone | `REVLoans.liquidateExpiredLoansFrom(...)` | None (permissionless after 10 years) |
|
|
504
|
-
| Anyone | `REVDeployer.autoIssueFor(...)` | None (permissionless after stage starts) |
|
|
505
|
-
| Anyone | `REVDeployer.burnHeldTokensOf(...)` | None |
|
|
506
|
-
| Split operator | `REVDeployer.setSplitOperatorOf(...)` | Must be current split operator |
|
|
507
|
-
| Split operator | `REVDeployer.deploySuckersFor(...)` | Must be current split operator + stage allows it |
|
|
508
|
-
| Contract owner | `REVLoans.setTokenUriResolver(...)` | Must be Ownable owner of REVLoans |
|
|
84
|
+
- Use [nana-core-v6](../nana-core-v6/USER_JOURNEYS.md) for the underlying project, terminal, and ruleset mechanics that Revnets package and constrain.
|
|
85
|
+
- Use [nana-721-hook-v6](../nana-721-hook-v6/USER_JOURNEYS.md), [nana-buyback-hook-v6](../nana-buyback-hook-v6/USER_JOURNEYS.md), and [nana-suckers-v6](../nana-suckers-v6/USER_JOURNEYS.md) when a Revnet deployment enables those optional features.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rev-net/core-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'revnet-core-v6'"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@bananapus/721-hook-v6": "^0.0.
|
|
22
|
+
"@bananapus/721-hook-v6": "^0.0.30",
|
|
23
23
|
"@bananapus/buyback-hook-v6": "^0.0.24",
|
|
24
24
|
"@bananapus/core-v6": "^0.0.30",
|
|
25
25
|
"@bananapus/ownable-v6": "^0.0.16",
|
|
26
26
|
"@bananapus/permission-ids-v6": "^0.0.15",
|
|
27
27
|
"@bananapus/router-terminal-v6": "^0.0.24",
|
|
28
28
|
"@bananapus/suckers-v6": "^0.0.20",
|
|
29
|
-
"@croptop/core-v6": "^0.0.
|
|
29
|
+
"@croptop/core-v6": "^0.0.28",
|
|
30
30
|
"@openzeppelin/contracts": "^5.6.1",
|
|
31
31
|
"@uniswap/v4-core": "^1.0.2",
|
|
32
32
|
"@uniswap/v4-periphery": "^1.0.3"
|