ens-budget 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +236 -2
- package/dist/abi.d.ts +158 -0
- package/dist/abi.js +123 -0
- package/dist/abi.js.map +1 -0
- package/dist/addresses.d.ts +30 -0
- package/dist/addresses.js +32 -0
- package/dist/addresses.js.map +1 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.js +64 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/read.d.ts +56 -0
- package/dist/read.js +73 -0
- package/dist/read.js.map +1 -0
- package/dist/solve.d.ts +63 -0
- package/dist/solve.js +113 -0
- package/dist/solve.js.map +1 -0
- package/dist/write.d.ts +28 -0
- package/dist/write.js +55 -0
- package/dist/write.js.map +1 -0
- package/package.json +59 -4
- package/src/abi.ts +125 -0
- package/src/addresses.ts +39 -0
- package/src/errors.ts +69 -0
- package/src/index.ts +19 -0
- package/src/read.ts +125 -0
- package/src/solve.ts +160 -0
- package/src/write.ts +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Namepass LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,237 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# ens-budget
|
|
4
|
+
|
|
5
|
+
**Renew ENS names by budget.**
|
|
6
|
+
|
|
7
|
+
Give it a label and a stablecoin amount. It returns the longest renewal that amount buys under ENS v2 pricing, checked against ENS's own quote, and can send the renewal for you.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/ens-budget)
|
|
10
|
+
[](https://github.com/wildcardlabs/ens-budget/actions/workflows/ci.yml)
|
|
11
|
+
[](https://bundlephobia.com/package/ens-budget)
|
|
12
|
+
[](https://www.npmjs.com/package/ens-budget)
|
|
13
|
+
[](https://viem.sh)
|
|
14
|
+
[](./LICENSE)
|
|
15
|
+
|
|
16
|
+
[Install](#install) · [Quick start](#quick-start) · [How it works](#how-it-works) · [Recipes](#recipes) · [API](#api) · [Networks](#networks)
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Exact.** Prices match ENS's `getRenewPrice` to the smallest token unit, including multi-year discounts.
|
|
25
|
+
- **Optimal.** Returns the longest affordable term even where a longer term is cheaper.
|
|
26
|
+
- **Complete.** Picks the right renewer, handles the token approval, and sends the renewal.
|
|
27
|
+
- **Composable.** Returns plain calls for EIP-5792 batching, smart accounts and relayers.
|
|
28
|
+
- **Small.** About 3 kB min+gzip. viem is the only dependency, as a peer.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
npm install ens-budget viem
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createPublicClient, http } from 'viem'
|
|
40
|
+
import { mainnet } from 'viem/chains'
|
|
41
|
+
import { addresses, quoteRenewal } from 'ens-budget'
|
|
42
|
+
|
|
43
|
+
const client = createPublicClient({ chain: mainnet, transport: http() })
|
|
44
|
+
|
|
45
|
+
const quote = await quoteRenewal(client, {
|
|
46
|
+
label: 'namepass', // namepass.eth
|
|
47
|
+
budget: 25_000000n, // $25 in USDC (6 decimals)
|
|
48
|
+
token: addresses[1].usdc,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
quote.duration // 143345079n seconds, about 4 years 6 months
|
|
52
|
+
quote.amount // 25000000n, the exact cost
|
|
53
|
+
quote.tier // { duration: 94608000n, discountBps: 3125 } 3-year rate, 31.25% off
|
|
54
|
+
quote.nextTier // { duration: 189216000n, extraAmount: 2000071n, … } 6 years for $2.00 more
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
To renew, pass a wallet client:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { renewWithBudget } from 'ens-budget'
|
|
61
|
+
|
|
62
|
+
const { hash, quote } = await renewWithBudget(walletClient, {
|
|
63
|
+
label: 'namepass',
|
|
64
|
+
budget: 25_000000n,
|
|
65
|
+
token: addresses[1].usdc,
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## How it works
|
|
70
|
+
|
|
71
|
+
ENS v2 prices renewals in USD per second, by label length, and discounts the whole term once it passes 2, 3 or 6 years. Because the discount applies to every second of the term, price is not monotonic in duration:
|
|
72
|
+
|
|
73
|
+
| `namepass.eth` | Price |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| 1 year | $8.00 |
|
|
76
|
+
| 5 years | $27.50 |
|
|
77
|
+
| 6 years | **$27.00** |
|
|
78
|
+
|
|
79
|
+
Dividing a budget by an annual rate gets this wrong. `quoteRenewal` instead:
|
|
80
|
+
|
|
81
|
+
1. Asks both ENS renewers `isRenewable(label)` and uses the one that says yes.
|
|
82
|
+
2. Reads the live rates, discount tiers and token exchange rate from the renewer's price oracle.
|
|
83
|
+
3. Solves for the longest duration whose exact price fits the budget, trying discount tiers from best to worst.
|
|
84
|
+
4. Confirms with `getRenewPrice` that ENS charges exactly the solved amount, and more than the budget for one second longer.
|
|
85
|
+
|
|
86
|
+
### Approvals
|
|
87
|
+
|
|
88
|
+
Renewals are paid in an ERC-20 token, so the renewer needs an allowance. `renewWithBudget` checks the current allowance and, only if it is short, approves **exactly** `quote.amount` and waits for confirmation before renewing. It never approves an unlimited amount or the full budget, so a price change between quote and send makes the renewal revert instead of overspending.
|
|
89
|
+
|
|
90
|
+
## Recipes
|
|
91
|
+
|
|
92
|
+
<details>
|
|
93
|
+
<summary><b>Batch approve and renew into one prompt (EIP-5792)</b></summary>
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { prepareRenewal, quoteRenewal } from 'ens-budget'
|
|
97
|
+
|
|
98
|
+
const quote = await quoteRenewal(client, { label, budget, token })
|
|
99
|
+
const calls = await prepareRenewal(client, quote, { account: wallet.account.address })
|
|
100
|
+
// [approve, renew], or [renew] when the allowance already covers it
|
|
101
|
+
|
|
102
|
+
await wallet.sendCalls({ calls })
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The same `{ to, data }` calls work with Safe transactions, bundlers and relayers.
|
|
106
|
+
|
|
107
|
+
</details>
|
|
108
|
+
|
|
109
|
+
<details>
|
|
110
|
+
<summary><b>Run on a server</b></summary>
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { createWalletClient, http, nonceManager, publicActions } from 'viem'
|
|
114
|
+
import { privateKeyToAccount } from 'viem/accounts'
|
|
115
|
+
|
|
116
|
+
const account = privateKeyToAccount(process.env.PRIVATE_KEY, { nonceManager })
|
|
117
|
+
const wallet = createWalletClient({ account, chain: mainnet, transport: http(RPC_URL) }).extend(publicActions)
|
|
118
|
+
|
|
119
|
+
await renewWithBudget(wallet, { label, budget, token })
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Exact approvals mean one `approve` transaction per renewal. For frequent renewals, set a standing allowance on the renewer yourself; `prepareRenewal` skips the approval whenever the allowance covers the price.
|
|
123
|
+
|
|
124
|
+
</details>
|
|
125
|
+
|
|
126
|
+
<details>
|
|
127
|
+
<summary><b>Price locally for live UIs</b></summary>
|
|
128
|
+
|
|
129
|
+
Read pricing once, then solve without network calls, for example on every slider move:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { getPricing, priceOf, solve } from 'ens-budget'
|
|
133
|
+
|
|
134
|
+
const pricing = await getPricing(client, { token })
|
|
135
|
+
|
|
136
|
+
solve(pricing, { label: 'namepass', budget: 25_000000n }) // { duration, amount, tier, nextTier }
|
|
137
|
+
priceOf(pricing, { label: 'namepass', duration: 31536000n }) // 8000021n
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
</details>
|
|
141
|
+
|
|
142
|
+
<details>
|
|
143
|
+
<summary><b>Normalize user input</b></summary>
|
|
144
|
+
|
|
145
|
+
Labels are used exactly as given, as in ensjs, so non-normalized legacy names stay renewable. Normalize anything a user types:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { normalize } from 'viem/ens'
|
|
149
|
+
|
|
150
|
+
const label = normalize(input).replace(/\.eth$/, '')
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Length is counted in Unicode code points of the label you pass, as ENS counts it. `❤️❤️❤️` with variation selectors is 6 code points and priced as a 5+ character name; `❤❤❤` is 3.
|
|
154
|
+
|
|
155
|
+
</details>
|
|
156
|
+
|
|
157
|
+
## API
|
|
158
|
+
|
|
159
|
+
### Network
|
|
160
|
+
|
|
161
|
+
| Function | Returns |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `quoteRenewal(client, { label, budget, token, renewers? })` | `Quote`: the longest renewal for the budget, confirmed with ENS. |
|
|
164
|
+
| `renewWithBudget(walletClient, { label, budget, token, referrer?, renewers? })` | `{ hash, quote }` after approving if needed and renewing. |
|
|
165
|
+
| `prepareRenewal(client, quote, { account, referrer? })` | `Call[]`: `[approve?, renew]` as `{ to, data }`. |
|
|
166
|
+
| `getPricing(client, { token, renewer?, renewers? })` | `Pricing`: oracle rates, discount tiers and token ratio. |
|
|
167
|
+
| `getRenewer(client, { label, renewers? })` | The renewer address for a label, or `null`. |
|
|
168
|
+
|
|
169
|
+
### Pure
|
|
170
|
+
|
|
171
|
+
| Function | Returns |
|
|
172
|
+
| --- | --- |
|
|
173
|
+
| `solve(pricing, { label, budget })` | `{ duration, amount, tier, nextTier }` |
|
|
174
|
+
| `priceOf(pricing, { label, duration })` | Exact cost in token units. |
|
|
175
|
+
|
|
176
|
+
All amounts are `bigint` in the token's smallest unit. All durations are `bigint` seconds.
|
|
177
|
+
|
|
178
|
+
<details>
|
|
179
|
+
<summary><b>Types</b></summary>
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
type Quote = {
|
|
183
|
+
label: string
|
|
184
|
+
renewer: Address // ETHRegistrar or ETHRenewerV1
|
|
185
|
+
token: Address
|
|
186
|
+
oracle: Address
|
|
187
|
+
budget: bigint
|
|
188
|
+
duration: bigint
|
|
189
|
+
amount: bigint // <= budget
|
|
190
|
+
tier: Tier | null // null at full price
|
|
191
|
+
nextTier: NextTier | null
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
type Tier = { duration: bigint; discountBps: number }
|
|
195
|
+
|
|
196
|
+
type NextTier = Tier & {
|
|
197
|
+
amount: bigint // cost of nextTier.duration
|
|
198
|
+
extraAmount: bigint // amount - budget
|
|
199
|
+
extraDuration: bigint // extra time gained by paying amount
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
</details>
|
|
204
|
+
|
|
205
|
+
### Errors
|
|
206
|
+
|
|
207
|
+
All errors extend `EnsBudgetError`.
|
|
208
|
+
|
|
209
|
+
| Error | Thrown when |
|
|
210
|
+
| --- | --- |
|
|
211
|
+
| `InvalidLabelError` | The label is empty, over 255 bytes, or a length ENS does not sell. |
|
|
212
|
+
| `NotRenewableError` | Neither renewer will renew the label (unregistered or past grace). |
|
|
213
|
+
| `UnsupportedTokenError` | The price oracle does not accept the token. |
|
|
214
|
+
| `UnsupportedChainError` | No built-in addresses for the chain and no `renewers` given. |
|
|
215
|
+
| `BudgetTooLowError` | The budget buys less than the minimum renewal. |
|
|
216
|
+
| `QuoteMismatchError` | The local price disagrees with ENS's quote. The renewal is not sent. |
|
|
217
|
+
|
|
218
|
+
## Networks
|
|
219
|
+
|
|
220
|
+
| Network | ETHRegistrar (v2 names) | ETHRenewerV1 (unmigrated v1 names) | Tokens |
|
|
221
|
+
| --- | --- | --- | --- |
|
|
222
|
+
| Mainnet | `0xD6D8369e55c94Ae8AFA714E6cF7AB5bE54D5CC81` | `0x56c447F308F463A01121C41E72b0E7e7557fCb68` | USDC, DAI |
|
|
223
|
+
| Sepolia | `0xf633e7FC17e2bbE0D0965D18ec1821dcB754a3d3` | `0xf2ece44980778966b8a0FccB3A9E339440f6e045` | USDC, DAI |
|
|
224
|
+
|
|
225
|
+
Token addresses are exported as `addresses[chainId].usdc` and `.dai`. For forks or other deployments, pass `renewers: { ethRegistrar, ethRenewerV1 }`. The price oracle is always read from the renewer, never hardcoded.
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```sh
|
|
230
|
+
npm test # unit tests, including a brute-force optimality check
|
|
231
|
+
npm run test:live # read-only checks against mainnet (MAINNET_RPC_URL optional)
|
|
232
|
+
npm run test:fork # real renewals on an anvil mainnet fork; requires Foundry
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
[MIT](./LICENSE) © Namepass LLC
|
package/dist/abi.d.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ABI fragments, inlined so the package carries only what it calls.
|
|
3
|
+
*
|
|
4
|
+
* `ETHRegistrar` (v2 names) and `ETHRenewerV1` (unmigrated v1 names) both
|
|
5
|
+
* implement `IETHRenewer`, so one fragment drives either.
|
|
6
|
+
*/
|
|
7
|
+
export declare const renewerAbi: readonly [{
|
|
8
|
+
readonly type: "function";
|
|
9
|
+
readonly name: "renew";
|
|
10
|
+
readonly stateMutability: "nonpayable";
|
|
11
|
+
readonly inputs: readonly [{
|
|
12
|
+
readonly name: "rd";
|
|
13
|
+
readonly type: "tuple";
|
|
14
|
+
readonly components: readonly [{
|
|
15
|
+
readonly name: "label";
|
|
16
|
+
readonly type: "string";
|
|
17
|
+
}, {
|
|
18
|
+
readonly name: "duration";
|
|
19
|
+
readonly type: "uint64";
|
|
20
|
+
}, {
|
|
21
|
+
readonly name: "referrer";
|
|
22
|
+
readonly type: "bytes32";
|
|
23
|
+
}];
|
|
24
|
+
}, {
|
|
25
|
+
readonly name: "paymentToken";
|
|
26
|
+
readonly type: "address";
|
|
27
|
+
}];
|
|
28
|
+
readonly outputs: readonly [];
|
|
29
|
+
}, {
|
|
30
|
+
readonly type: "function";
|
|
31
|
+
readonly name: "isRenewable";
|
|
32
|
+
readonly stateMutability: "view";
|
|
33
|
+
readonly inputs: readonly [{
|
|
34
|
+
readonly name: "label";
|
|
35
|
+
readonly type: "string";
|
|
36
|
+
}];
|
|
37
|
+
readonly outputs: readonly [{
|
|
38
|
+
readonly name: "";
|
|
39
|
+
readonly type: "bool";
|
|
40
|
+
}];
|
|
41
|
+
}, {
|
|
42
|
+
readonly type: "function";
|
|
43
|
+
readonly name: "getRenewPrice";
|
|
44
|
+
readonly stateMutability: "view";
|
|
45
|
+
readonly inputs: readonly [{
|
|
46
|
+
readonly name: "label";
|
|
47
|
+
readonly type: "string";
|
|
48
|
+
}, {
|
|
49
|
+
readonly name: "duration";
|
|
50
|
+
readonly type: "uint64";
|
|
51
|
+
}, {
|
|
52
|
+
readonly name: "paymentToken";
|
|
53
|
+
readonly type: "address";
|
|
54
|
+
}];
|
|
55
|
+
readonly outputs: readonly [{
|
|
56
|
+
readonly name: "";
|
|
57
|
+
readonly type: "uint256";
|
|
58
|
+
}];
|
|
59
|
+
}, {
|
|
60
|
+
readonly type: "function";
|
|
61
|
+
readonly name: "rentPriceOracle";
|
|
62
|
+
readonly stateMutability: "view";
|
|
63
|
+
readonly inputs: readonly [];
|
|
64
|
+
readonly outputs: readonly [{
|
|
65
|
+
readonly name: "";
|
|
66
|
+
readonly type: "address";
|
|
67
|
+
}];
|
|
68
|
+
}, {
|
|
69
|
+
readonly type: "function";
|
|
70
|
+
readonly name: "MIN_RENEW_DURATION";
|
|
71
|
+
readonly stateMutability: "view";
|
|
72
|
+
readonly inputs: readonly [];
|
|
73
|
+
readonly outputs: readonly [{
|
|
74
|
+
readonly name: "";
|
|
75
|
+
readonly type: "uint64";
|
|
76
|
+
}];
|
|
77
|
+
}];
|
|
78
|
+
export declare const oracleAbi: readonly [{
|
|
79
|
+
readonly type: "function";
|
|
80
|
+
readonly name: "DISCOUNT_DENOMINATOR";
|
|
81
|
+
readonly stateMutability: "view";
|
|
82
|
+
readonly inputs: readonly [];
|
|
83
|
+
readonly outputs: readonly [{
|
|
84
|
+
readonly name: "";
|
|
85
|
+
readonly type: "uint128";
|
|
86
|
+
}];
|
|
87
|
+
}, {
|
|
88
|
+
readonly type: "function";
|
|
89
|
+
readonly name: "getBaseRates";
|
|
90
|
+
readonly stateMutability: "view";
|
|
91
|
+
readonly inputs: readonly [];
|
|
92
|
+
readonly outputs: readonly [{
|
|
93
|
+
readonly name: "";
|
|
94
|
+
readonly type: "uint256[]";
|
|
95
|
+
}];
|
|
96
|
+
}, {
|
|
97
|
+
readonly type: "function";
|
|
98
|
+
readonly name: "getDiscountPoints";
|
|
99
|
+
readonly stateMutability: "view";
|
|
100
|
+
readonly inputs: readonly [];
|
|
101
|
+
readonly outputs: readonly [{
|
|
102
|
+
readonly name: "";
|
|
103
|
+
readonly type: "tuple[]";
|
|
104
|
+
readonly components: readonly [{
|
|
105
|
+
readonly name: "duration";
|
|
106
|
+
readonly type: "uint64";
|
|
107
|
+
}, {
|
|
108
|
+
readonly name: "numer";
|
|
109
|
+
readonly type: "uint128";
|
|
110
|
+
}];
|
|
111
|
+
}];
|
|
112
|
+
}, {
|
|
113
|
+
readonly type: "function";
|
|
114
|
+
readonly name: "getPaymentTokenRatio";
|
|
115
|
+
readonly stateMutability: "view";
|
|
116
|
+
readonly inputs: readonly [{
|
|
117
|
+
readonly name: "paymentToken";
|
|
118
|
+
readonly type: "address";
|
|
119
|
+
}];
|
|
120
|
+
readonly outputs: readonly [{
|
|
121
|
+
readonly name: "numer";
|
|
122
|
+
readonly type: "uint128";
|
|
123
|
+
}, {
|
|
124
|
+
readonly name: "denom";
|
|
125
|
+
readonly type: "uint128";
|
|
126
|
+
}];
|
|
127
|
+
}];
|
|
128
|
+
export declare const erc20Abi: readonly [{
|
|
129
|
+
readonly type: "function";
|
|
130
|
+
readonly name: "allowance";
|
|
131
|
+
readonly stateMutability: "view";
|
|
132
|
+
readonly inputs: readonly [{
|
|
133
|
+
readonly name: "owner";
|
|
134
|
+
readonly type: "address";
|
|
135
|
+
}, {
|
|
136
|
+
readonly name: "spender";
|
|
137
|
+
readonly type: "address";
|
|
138
|
+
}];
|
|
139
|
+
readonly outputs: readonly [{
|
|
140
|
+
readonly name: "";
|
|
141
|
+
readonly type: "uint256";
|
|
142
|
+
}];
|
|
143
|
+
}, {
|
|
144
|
+
readonly type: "function";
|
|
145
|
+
readonly name: "approve";
|
|
146
|
+
readonly stateMutability: "nonpayable";
|
|
147
|
+
readonly inputs: readonly [{
|
|
148
|
+
readonly name: "spender";
|
|
149
|
+
readonly type: "address";
|
|
150
|
+
}, {
|
|
151
|
+
readonly name: "amount";
|
|
152
|
+
readonly type: "uint256";
|
|
153
|
+
}];
|
|
154
|
+
readonly outputs: readonly [{
|
|
155
|
+
readonly name: "";
|
|
156
|
+
readonly type: "bool";
|
|
157
|
+
}];
|
|
158
|
+
}];
|
package/dist/abi.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ABI fragments, inlined so the package carries only what it calls.
|
|
3
|
+
*
|
|
4
|
+
* `ETHRegistrar` (v2 names) and `ETHRenewerV1` (unmigrated v1 names) both
|
|
5
|
+
* implement `IETHRenewer`, so one fragment drives either.
|
|
6
|
+
*/
|
|
7
|
+
export const renewerAbi = [
|
|
8
|
+
{
|
|
9
|
+
type: 'function',
|
|
10
|
+
name: 'renew',
|
|
11
|
+
stateMutability: 'nonpayable',
|
|
12
|
+
inputs: [
|
|
13
|
+
{
|
|
14
|
+
name: 'rd',
|
|
15
|
+
type: 'tuple',
|
|
16
|
+
components: [
|
|
17
|
+
{ name: 'label', type: 'string' },
|
|
18
|
+
{ name: 'duration', type: 'uint64' },
|
|
19
|
+
{ name: 'referrer', type: 'bytes32' },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{ name: 'paymentToken', type: 'address' },
|
|
23
|
+
],
|
|
24
|
+
outputs: [],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: 'function',
|
|
28
|
+
name: 'isRenewable',
|
|
29
|
+
stateMutability: 'view',
|
|
30
|
+
inputs: [{ name: 'label', type: 'string' }],
|
|
31
|
+
outputs: [{ name: '', type: 'bool' }],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: 'function',
|
|
35
|
+
name: 'getRenewPrice',
|
|
36
|
+
stateMutability: 'view',
|
|
37
|
+
inputs: [
|
|
38
|
+
{ name: 'label', type: 'string' },
|
|
39
|
+
{ name: 'duration', type: 'uint64' },
|
|
40
|
+
{ name: 'paymentToken', type: 'address' },
|
|
41
|
+
],
|
|
42
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: 'function',
|
|
46
|
+
name: 'rentPriceOracle',
|
|
47
|
+
stateMutability: 'view',
|
|
48
|
+
inputs: [],
|
|
49
|
+
outputs: [{ name: '', type: 'address' }],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'function',
|
|
53
|
+
name: 'MIN_RENEW_DURATION',
|
|
54
|
+
stateMutability: 'view',
|
|
55
|
+
inputs: [],
|
|
56
|
+
outputs: [{ name: '', type: 'uint64' }],
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
export const oracleAbi = [
|
|
60
|
+
{
|
|
61
|
+
type: 'function',
|
|
62
|
+
name: 'DISCOUNT_DENOMINATOR',
|
|
63
|
+
stateMutability: 'view',
|
|
64
|
+
inputs: [],
|
|
65
|
+
outputs: [{ name: '', type: 'uint128' }],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'function',
|
|
69
|
+
name: 'getBaseRates',
|
|
70
|
+
stateMutability: 'view',
|
|
71
|
+
inputs: [],
|
|
72
|
+
outputs: [{ name: '', type: 'uint256[]' }],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
type: 'function',
|
|
76
|
+
name: 'getDiscountPoints',
|
|
77
|
+
stateMutability: 'view',
|
|
78
|
+
inputs: [],
|
|
79
|
+
outputs: [
|
|
80
|
+
{
|
|
81
|
+
name: '',
|
|
82
|
+
type: 'tuple[]',
|
|
83
|
+
components: [
|
|
84
|
+
{ name: 'duration', type: 'uint64' },
|
|
85
|
+
{ name: 'numer', type: 'uint128' },
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: 'function',
|
|
92
|
+
name: 'getPaymentTokenRatio',
|
|
93
|
+
stateMutability: 'view',
|
|
94
|
+
inputs: [{ name: 'paymentToken', type: 'address' }],
|
|
95
|
+
outputs: [
|
|
96
|
+
{ name: 'numer', type: 'uint128' },
|
|
97
|
+
{ name: 'denom', type: 'uint128' },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
export const erc20Abi = [
|
|
102
|
+
{
|
|
103
|
+
type: 'function',
|
|
104
|
+
name: 'allowance',
|
|
105
|
+
stateMutability: 'view',
|
|
106
|
+
inputs: [
|
|
107
|
+
{ name: 'owner', type: 'address' },
|
|
108
|
+
{ name: 'spender', type: 'address' },
|
|
109
|
+
],
|
|
110
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
type: 'function',
|
|
114
|
+
name: 'approve',
|
|
115
|
+
stateMutability: 'nonpayable',
|
|
116
|
+
inputs: [
|
|
117
|
+
{ name: 'spender', type: 'address' },
|
|
118
|
+
{ name: 'amount', type: 'uint256' },
|
|
119
|
+
],
|
|
120
|
+
outputs: [{ name: '', type: 'bool' }],
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
//# sourceMappingURL=abi.js.map
|
package/dist/abi.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abi.js","sourceRoot":"","sources":["../src/abi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Address } from 'viem';
|
|
2
|
+
export type Renewers = {
|
|
3
|
+
/** v2 `ETHRegistrar`: renews names registered on, or migrated to, v2. */
|
|
4
|
+
ethRegistrar: Address;
|
|
5
|
+
/** `ETHRenewerV1`: renews unmigrated legacy (v1) names. */
|
|
6
|
+
ethRenewerV1: Address;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Deployed ENS v2 renewers and the stablecoins ENS prices, per chain.
|
|
10
|
+
*
|
|
11
|
+
* Sourced from `@ensdomains/ensjs` and checked on-chain: both renewers expose
|
|
12
|
+
* `renew((string,uint64,bytes32),address)` and neither is an upgradeable
|
|
13
|
+
* proxy. The rent price oracle is *not* listed here; renewers can point at a
|
|
14
|
+
* new one, so it is read from `rentPriceOracle()` every time.
|
|
15
|
+
*/
|
|
16
|
+
export declare const addresses: {
|
|
17
|
+
readonly 1: {
|
|
18
|
+
readonly ethRegistrar: "0xD6D8369e55c94Ae8AFA714E6cF7AB5bE54D5CC81";
|
|
19
|
+
readonly ethRenewerV1: "0x56c447F308F463A01121C41E72b0E7e7557fCb68";
|
|
20
|
+
readonly usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
|
21
|
+
readonly dai: "0x6B175474E89094C44Da98b954EedeAC495271d0F";
|
|
22
|
+
};
|
|
23
|
+
readonly 11155111: {
|
|
24
|
+
readonly ethRegistrar: "0xf633e7FC17e2bbE0D0965D18ec1821dcB754a3d3";
|
|
25
|
+
readonly ethRenewerV1: "0xf2ece44980778966b8a0FccB3A9E339440f6e045";
|
|
26
|
+
readonly usdc: "0x240b0316Df57887DBBE58b586508b19e633a14aa";
|
|
27
|
+
readonly dai: "0xF6faC8A58a0BE13b9197f27C41B73162Fe32572b";
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export declare function renewersFor(chainId: number | undefined, override?: Renewers): Renewers;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { UnsupportedChainError } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deployed ENS v2 renewers and the stablecoins ENS prices, per chain.
|
|
4
|
+
*
|
|
5
|
+
* Sourced from `@ensdomains/ensjs` and checked on-chain: both renewers expose
|
|
6
|
+
* `renew((string,uint64,bytes32),address)` and neither is an upgradeable
|
|
7
|
+
* proxy. The rent price oracle is *not* listed here; renewers can point at a
|
|
8
|
+
* new one, so it is read from `rentPriceOracle()` every time.
|
|
9
|
+
*/
|
|
10
|
+
export const addresses = {
|
|
11
|
+
1: {
|
|
12
|
+
ethRegistrar: '0xD6D8369e55c94Ae8AFA714E6cF7AB5bE54D5CC81',
|
|
13
|
+
ethRenewerV1: '0x56c447F308F463A01121C41E72b0E7e7557fCb68',
|
|
14
|
+
usdc: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
|
15
|
+
dai: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
|
16
|
+
},
|
|
17
|
+
11155111: {
|
|
18
|
+
ethRegistrar: '0xf633e7FC17e2bbE0D0965D18ec1821dcB754a3d3',
|
|
19
|
+
ethRenewerV1: '0xf2ece44980778966b8a0FccB3A9E339440f6e045',
|
|
20
|
+
usdc: '0x240b0316Df57887DBBE58b586508b19e633a14aa',
|
|
21
|
+
dai: '0xF6faC8A58a0BE13b9197f27C41B73162Fe32572b',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
export function renewersFor(chainId, override) {
|
|
25
|
+
if (override)
|
|
26
|
+
return override;
|
|
27
|
+
const known = chainId === undefined ? undefined : addresses[chainId];
|
|
28
|
+
if (!known)
|
|
29
|
+
throw new UnsupportedChainError(chainId);
|
|
30
|
+
return known;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=addresses.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addresses.js","sourceRoot":"","sources":["../src/addresses.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AASnD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,CAAC,EAAE;QACD,YAAY,EAAE,4CAA4C;QAC1D,YAAY,EAAE,4CAA4C;QAC1D,IAAI,EAAE,4CAA4C;QAClD,GAAG,EAAE,4CAA4C;KAClD;IACD,QAAQ,EAAE;QACR,YAAY,EAAE,4CAA4C;QAC1D,YAAY,EAAE,4CAA4C;QAC1D,IAAI,EAAE,4CAA4C;QAClD,GAAG,EAAE,4CAA4C;KAClD;CAC4E,CAAA;AAE/E,MAAM,UAAU,WAAW,CAAC,OAA2B,EAAE,QAAmB;IAC1E,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAC7B,MAAM,KAAK,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,OAAiC,CAAC,CAAA;IAC9F,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,qBAAqB,CAAC,OAAO,CAAC,CAAA;IACpD,OAAO,KAAK,CAAA;AACd,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Address } from 'viem';
|
|
2
|
+
export declare class EnsBudgetError extends Error {
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
/** Empty, over 255 bytes, or a length ENS doesn't sell (e.g. 1–2 characters). */
|
|
6
|
+
export declare class InvalidLabelError extends EnsBudgetError {
|
|
7
|
+
readonly label: string;
|
|
8
|
+
name: string;
|
|
9
|
+
constructor(label: string, reason: string);
|
|
10
|
+
}
|
|
11
|
+
/** Neither renewer will renew the label right now (unregistered, or past grace). */
|
|
12
|
+
export declare class NotRenewableError extends EnsBudgetError {
|
|
13
|
+
readonly label: string;
|
|
14
|
+
name: string;
|
|
15
|
+
constructor(label: string);
|
|
16
|
+
}
|
|
17
|
+
/** The rent price oracle has no exchange rate for this token. */
|
|
18
|
+
export declare class UnsupportedTokenError extends EnsBudgetError {
|
|
19
|
+
readonly token: Address;
|
|
20
|
+
name: string;
|
|
21
|
+
constructor(token: Address);
|
|
22
|
+
}
|
|
23
|
+
/** No built-in renewer addresses for this chain; pass `renewers`. */
|
|
24
|
+
export declare class UnsupportedChainError extends EnsBudgetError {
|
|
25
|
+
readonly chainId: number | undefined;
|
|
26
|
+
name: string;
|
|
27
|
+
constructor(chainId: number | undefined);
|
|
28
|
+
}
|
|
29
|
+
/** The budget buys less than the renewer's minimum duration. */
|
|
30
|
+
export declare class BudgetTooLowError extends EnsBudgetError {
|
|
31
|
+
readonly budget: bigint;
|
|
32
|
+
readonly duration: bigint;
|
|
33
|
+
readonly minDuration: bigint;
|
|
34
|
+
name: string;
|
|
35
|
+
constructor(budget: bigint, duration: bigint, minDuration: bigint);
|
|
36
|
+
}
|
|
37
|
+
/** The local solve disagrees with ENS's own `getRenewPrice`. Should never happen. */
|
|
38
|
+
export declare class QuoteMismatchError extends EnsBudgetError {
|
|
39
|
+
readonly expected: bigint;
|
|
40
|
+
readonly actual: bigint;
|
|
41
|
+
name: string;
|
|
42
|
+
constructor(expected: bigint, actual: bigint);
|
|
43
|
+
}
|