ens-budget 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +71 -9
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -8,10 +8,10 @@ Give it a label and a stablecoin amount. It returns the longest renewal that amo
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/ens-budget?style=flat-square&color=0080BC)](https://www.npmjs.com/package/ens-budget)
10
10
  [![CI](https://img.shields.io/github/actions/workflow/status/wildcardlabs/ens-budget/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/wildcardlabs/ens-budget/actions/workflows/ci.yml)
11
- [![min+gzip](https://img.shields.io/bundlephobia/minzip/ens-budget?style=flat-square&label=min%2Bgzip)](https://bundlephobia.com/package/ens-budget)
11
+ [![size](https://deno.bundlejs.com/badge?q=ens-budget&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22viem%22%5D%7D%7D&badge-style=flat-square)](https://bundlejs.com/?q=ens-budget&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22viem%22%5D%7D%7D)
12
12
  [![types](https://img.shields.io/npm/types/ens-budget?style=flat-square)](https://www.npmjs.com/package/ens-budget)
13
13
  [![viem](https://img.shields.io/npm/dependency-version/ens-budget/peer/viem?style=flat-square)](https://viem.sh)
14
- [![license](https://img.shields.io/npm/l/ens-budget?style=flat-square)](./LICENSE)
14
+ [![license](https://img.shields.io/github/license/wildcardlabs/ens-budget?style=flat-square)](./LICENSE)
15
15
 
16
16
  [Install](#install) · [Quick start](#quick-start) · [How it works](#how-it-works) · [Recipes](#recipes) · [API](#api) · [Networks](#networks)
17
17
 
@@ -51,10 +51,10 @@ const quote = await quoteRenewal(client, {
51
51
  quote.duration // 143345079n seconds, about 4 years 6 months
52
52
  quote.amount // 25000000n, the exact cost
53
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
54
+ quote.nextTier // { duration: 189216000n, extraAmount: 2000071n, … } 6 years for $2.01 more
55
55
  ```
56
56
 
57
- To renew, pass a wallet client:
57
+ To renew, pass a viem wallet client with an `account` (using wagmi? see [Renew with wagmi](#recipes)):
58
58
 
59
59
  ```ts
60
60
  import { renewWithBudget } from 'ens-budget'
@@ -68,13 +68,17 @@ const { hash, quote } = await renewWithBudget(walletClient, {
68
68
 
69
69
  ## How it works
70
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:
71
+ ENS v2 prices renewals in USD per second, by label length, and discounts the whole term once it reaches 2, 3 or 6 years. Because the discount applies to every second of the term, a term just short of a tier can cost more than the tier itself:
72
72
 
73
73
  | `namepass.eth` | Price |
74
74
  | --- | --- |
75
- | 1 year | $8.00 |
76
- | 5 years | $27.50 |
77
- | 6 years | **$27.00** |
75
+ | 1 year | $8.01 |
76
+ | 1 year 364 days | $15.98 |
77
+ | **2 years** | **$14.01** |
78
+ | 5 years 364 days | $32.99 |
79
+ | **6 years** | **$27.01** |
80
+
81
+ Prices are rounded up to the cent. ENS charges exact token units, a fraction of a cent above the round figure (6 years is 27.000071 USDC), so $27.00 buys about 4 years 11 months and $27.01 buys 6 years.
78
82
 
79
83
  Dividing a budget by an annual rate gets this wrong. `quoteRenewal` instead:
80
84
 
@@ -89,6 +93,64 @@ Renewals are paid in an ERC-20 token, so the renewer needs an allowance. `renewW
89
93
 
90
94
  ## Recipes
91
95
 
96
+ <details>
97
+ <summary><b>Warn when a chosen date costs more than a longer term</b></summary>
98
+
99
+ Apps with a "renew until" date picker can catch terms that fall just short of a tier:
100
+
101
+ ```ts
102
+ import { getPricing, priceOf, solve } from 'ens-budget'
103
+
104
+ const pricing = await getPricing(client, { token }) // once
105
+
106
+ const cost = priceOf(pricing, { label, duration }) // the term the user picked
107
+ const best = solve(pricing, { label, budget: cost })
108
+
109
+ if (best.tier && best.tier.duration > duration) {
110
+ // e.g. "6 years costs $27.01, less than the $32.99 you selected"
111
+ const tierCost = priceOf(pricing, { label, duration: best.tier.duration })
112
+ }
113
+ ```
114
+
115
+ `priceOf` and `solve` run locally, so this can run on every date change.
116
+
117
+ </details>
118
+
119
+ <details>
120
+ <summary><b>Offer a top-up to the next tier</b></summary>
121
+
122
+ When a budget lands just short of a tier, `quote.nextTier` says how much more reaches it:
123
+
124
+ ```ts
125
+ const { nextTier } = await quoteRenewal(client, { label, budget: 26_000000n, token })
126
+ // nextTier.extraAmount 1000071n, about $1.01
127
+ // nextTier.extraDuration about 15 more months
128
+ ```
129
+
130
+ </details>
131
+
132
+ <details>
133
+ <summary><b>Renew with wagmi</b></summary>
134
+
135
+ `renewWithBudget` needs a viem wallet client with an `account`. With wagmi it is simpler to quote with the public client, build the calls with `prepareRenewal`, and send them through wagmi:
136
+
137
+ ```ts
138
+ import { useAccount, usePublicClient, useSendCalls } from 'wagmi'
139
+ import { prepareRenewal, quoteRenewal } from 'ens-budget'
140
+
141
+ const client = usePublicClient()
142
+ const { address } = useAccount()
143
+ const { sendCallsAsync } = useSendCalls()
144
+
145
+ const quote = await quoteRenewal(client, { label, budget, token })
146
+ const calls = await prepareRenewal(client, quote, { account: address })
147
+ await sendCallsAsync({ calls })
148
+ ```
149
+
150
+ For wallets without EIP-5792, send the calls in order with `useSendTransaction`, and wait for the `approve` receipt before sending `renew`.
151
+
152
+ </details>
153
+
92
154
  <details>
93
155
  <summary><b>Batch approve and renew into one prompt (EIP-5792)</b></summary>
94
156
 
@@ -161,7 +223,7 @@ Length is counted in Unicode code points of the label you pass, as ENS counts it
161
223
  | Function | Returns |
162
224
  | --- | --- |
163
225
  | `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. |
226
+ | `renewWithBudget(walletClient, { label, budget, token, referrer?, renewers? })` | `{ hash, quote }` after approving if needed and renewing. Needs a wallet client with an `account`. |
165
227
  | `prepareRenewal(client, quote, { account, referrer? })` | `Call[]`: `[approve?, renew]` as `{ to, data }`. |
166
228
  | `getPricing(client, { token, renewer?, renewers? })` | `Pricing`: oracle rates, discount tiers and token ratio. |
167
229
  | `getRenewer(client, { label, renewers? })` | The renewer address for a label, or `null`. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ens-budget",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Budget-first ENS v2 renewals: turn a stablecoin budget into the longest renewal it buys, then renew.",
5
5
  "license": "MIT",
6
6
  "author": "Namepass LLC <steve@namepass.com>",