@hoodfi/sdk 0.0.0-stage → 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.
- package/README.md +103 -2
- package/dist/abi.d.ts +106 -0
- package/dist/abi.d.ts.map +1 -0
- package/dist/abi.js +34 -0
- package/dist/abi.js.map +1 -0
- package/dist/client.d.ts +145 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +304 -0
- package/dist/client.js.map +1 -0
- package/dist/coins.d.ts +34 -0
- package/dist/coins.d.ts.map +1 -0
- package/dist/coins.js +53 -0
- package/dist/coins.js.map +1 -0
- package/dist/constants.d.ts +31 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +38 -0
- package/dist/constants.js.map +1 -0
- package/dist/contenthash.d.ts +96 -0
- package/dist/contenthash.d.ts.map +1 -0
- package/dist/contenthash.js +385 -0
- package/dist/contenthash.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/labels.d.ts +45 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/labels.js +83 -0
- package/dist/labels.js.map +1 -0
- package/dist/xrp.d.ts +24 -0
- package/dist/xrp.d.ts.map +1 -0
- package/dist/xrp.js +43 -0
- package/dist/xrp.js.map +1 -0
- package/package.json +41 -4
- package/src/abi.ts +35 -0
- package/src/client.ts +481 -0
- package/src/coins.ts +76 -0
- package/src/constants.ts +41 -0
- package/src/contenthash.ts +408 -0
- package/src/index.ts +30 -0
- package/src/labels.ts +91 -0
- package/src/xrp.ts +44 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { createPublicClient, encodeFunctionData, erc20Abi, fallback, getAddress, http, isAddress, namehash, } from 'viem';
|
|
2
|
+
import { registrarAbi, registryAbi } from './abi.js';
|
|
3
|
+
import { CHAINS, paymentCaveat } from './coins.js';
|
|
4
|
+
import { CONTRACTS, DEFAULT_API_URL, DEFAULT_ROBINHOOD_RPC, FALLBACK_ROBINHOOD_RPC, ROBINHOOD_CHAIN_ID, ROBINHOOD_EXPLORER, robinhoodChain, } from './constants.js';
|
|
5
|
+
import { contentGatewayUrl, decodeContenthash } from './contenthash.js';
|
|
6
|
+
import { STATUS_TEXT, checkHoodfiName, checkLabel, fullName, tierOf } from './labels.js';
|
|
7
|
+
export class HoodfiError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code, message, options) {
|
|
10
|
+
super(message, options);
|
|
11
|
+
this.name = 'HoodfiError';
|
|
12
|
+
this.code = code;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const TEXT_KEYS = ['avatar', 'description', 'url', 'com.twitter', 'com.github', 'email'];
|
|
16
|
+
const UNREADABLE_MSG = 'Could not read Robinhood Chain: the reads failed rather than coming back empty, so this says nothing about the name. Try again shortly.';
|
|
17
|
+
function unavailableReason(status, label) {
|
|
18
|
+
switch (status) {
|
|
19
|
+
case 0:
|
|
20
|
+
return undefined;
|
|
21
|
+
case 1:
|
|
22
|
+
return `${fullName(label)} is already registered.`;
|
|
23
|
+
case 2:
|
|
24
|
+
return `${fullName(label)} is locked and not on sale right now.`;
|
|
25
|
+
case 3:
|
|
26
|
+
return `${label} is not a valid label.`;
|
|
27
|
+
case 4:
|
|
28
|
+
return `${label} is reserved and can never be registered.`;
|
|
29
|
+
default:
|
|
30
|
+
return `${fullName(label)} is not available (status ${status}).`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A batch where nothing succeeded is a dead connection, not an answer.
|
|
35
|
+
*
|
|
36
|
+
* With `allowFailure: true`, a failed aggregate call (a 429 from a throttled RPC) marks
|
|
37
|
+
* every entry as failed, exactly like each one reverting. For a never-minted name only
|
|
38
|
+
* `ownerOf` reverts; `addr`/`text`/`contenthash` return empty successfully. So "all
|
|
39
|
+
* failed" can only mean the transport.
|
|
40
|
+
*/
|
|
41
|
+
function batchFailed(results) {
|
|
42
|
+
return results.every((r) => r.status === 'failure');
|
|
43
|
+
}
|
|
44
|
+
function requireName(input) {
|
|
45
|
+
if (typeof input !== 'string')
|
|
46
|
+
throw new HoodfiError('INVALID_NAME', 'Name must be a string.');
|
|
47
|
+
const checked = checkHoodfiName(input);
|
|
48
|
+
if (!checked.ok)
|
|
49
|
+
throw new HoodfiError('INVALID_NAME', checked.reason);
|
|
50
|
+
return checked;
|
|
51
|
+
}
|
|
52
|
+
function requireLabel(input) {
|
|
53
|
+
if (typeof input !== 'string')
|
|
54
|
+
throw new HoodfiError('INVALID_NAME', 'Name must be a string.');
|
|
55
|
+
const checked = checkLabel(input);
|
|
56
|
+
if (!checked.ok)
|
|
57
|
+
throw new HoodfiError('INVALID_NAME', checked.reason);
|
|
58
|
+
return checked.label;
|
|
59
|
+
}
|
|
60
|
+
function requireAddress(value) {
|
|
61
|
+
if (typeof value !== 'string' || !isAddress(value, { strict: false }))
|
|
62
|
+
throw new HoodfiError('INVALID_NAME', 'Expected a 0x-prefixed 20-byte address.');
|
|
63
|
+
return getAddress(value);
|
|
64
|
+
}
|
|
65
|
+
export function createHoodfiClient(options = {}) {
|
|
66
|
+
const client = options.publicClient ??
|
|
67
|
+
createPublicClient({
|
|
68
|
+
chain: robinhoodChain,
|
|
69
|
+
transport: fallback([...(options.rpcUrls ?? []), DEFAULT_ROBINHOOD_RPC, FALLBACK_ROBINHOOD_RPC].map((url) => http(url, { retryCount: 2, retryDelay: 200, timeout: 8_000 })),
|
|
70
|
+
// Ranking probes every endpoint in the background, spending the rate limit
|
|
71
|
+
// this fallback exists to conserve.
|
|
72
|
+
{ rank: false }),
|
|
73
|
+
});
|
|
74
|
+
const apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/+$/, '');
|
|
75
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
76
|
+
async function readRegistrar(label) {
|
|
77
|
+
try {
|
|
78
|
+
// allowFailure: false: none of these can revert for a valid label, so a failure
|
|
79
|
+
// is the RPC, and must not be folded into an answer.
|
|
80
|
+
const [status, prices, paused] = await client.multicall({
|
|
81
|
+
contracts: [
|
|
82
|
+
{ address: CONTRACTS.registrar, abi: registrarAbi, functionName: 'status', args: [label] },
|
|
83
|
+
{ address: CONTRACTS.registrar, abi: registrarAbi, functionName: 'priceOf', args: [label] },
|
|
84
|
+
{ address: CONTRACTS.registrar, abi: registrarAbi, functionName: 'paused' },
|
|
85
|
+
],
|
|
86
|
+
allowFailure: false,
|
|
87
|
+
});
|
|
88
|
+
return { status, wei: prices[0], usdg: prices[1], paused };
|
|
89
|
+
}
|
|
90
|
+
catch (cause) {
|
|
91
|
+
throw new HoodfiError('UNREADABLE', UNREADABLE_MSG, { cause });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Is a name free, and what does it cost? Prices are read from the registrar on each call. */
|
|
95
|
+
async function checkName(name) {
|
|
96
|
+
const label = requireLabel(name);
|
|
97
|
+
const { status, wei, usdg, paused } = await readRegistrar(label);
|
|
98
|
+
const reason = paused ? 'Minting is paused by the registrar owner.' : unavailableReason(status, label);
|
|
99
|
+
return {
|
|
100
|
+
name: fullName(label),
|
|
101
|
+
label,
|
|
102
|
+
length: label.length,
|
|
103
|
+
tier: tierOf(label),
|
|
104
|
+
status: STATUS_TEXT[status] ?? 'invalid',
|
|
105
|
+
available: status === 0 && !paused,
|
|
106
|
+
paused,
|
|
107
|
+
...(reason !== undefined && { reason }),
|
|
108
|
+
price: { wei, usdg },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Everything a name holds, in one RPC round-trip. Returns null when the name was never
|
|
113
|
+
* minted. Throws UNREADABLE (never null) when the chain didn't answer.
|
|
114
|
+
*
|
|
115
|
+
* Accepts `gm`, `gm.hoodfi.eth` or a subname like `jack.gm.hoodfi.eth`. Does not accept
|
|
116
|
+
* `gm.eth`: that's a different name on Ethereum.
|
|
117
|
+
*/
|
|
118
|
+
async function resolve(name) {
|
|
119
|
+
const checked = requireName(name);
|
|
120
|
+
const node = namehash(checked.name);
|
|
121
|
+
const r = CONTRACTS.registry;
|
|
122
|
+
const addrAt = (coinType) => ({ address: r, abi: registryAbi, functionName: 'addr', args: [node, coinType] });
|
|
123
|
+
const text = (key) => ({ address: r, abi: registryAbi, functionName: 'text', args: [node, key] });
|
|
124
|
+
let results;
|
|
125
|
+
try {
|
|
126
|
+
results = await client.multicall({
|
|
127
|
+
contracts: [
|
|
128
|
+
{ address: r, abi: registryAbi, functionName: 'ownerOf', args: [BigInt(node)] },
|
|
129
|
+
addrAt(CHAINS.robinhood.coinType),
|
|
130
|
+
addrAt(CHAINS.ethereum.coinType),
|
|
131
|
+
addrAt(CHAINS.bitcoin.coinType),
|
|
132
|
+
addrAt(CHAINS.solana.coinType),
|
|
133
|
+
addrAt(CHAINS.xrp.coinType),
|
|
134
|
+
{ address: r, abi: registryAbi, functionName: 'contenthash', args: [node] },
|
|
135
|
+
text('avatar'),
|
|
136
|
+
text('description'),
|
|
137
|
+
text('url'),
|
|
138
|
+
text('com.twitter'),
|
|
139
|
+
text('com.github'),
|
|
140
|
+
text('email'),
|
|
141
|
+
],
|
|
142
|
+
allowFailure: true,
|
|
143
|
+
// viem splits a multicall past 1024 bytes of calldata by default; these 13 reads
|
|
144
|
+
// are ~1.2 KB. One request is the point: two is the shape public RPCs throttle.
|
|
145
|
+
batchSize: 0,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
catch (cause) {
|
|
149
|
+
throw new HoodfiError('UNREADABLE', UNREADABLE_MSG, { cause });
|
|
150
|
+
}
|
|
151
|
+
if (batchFailed(results))
|
|
152
|
+
throw new HoodfiError('UNREADABLE', UNREADABLE_MSG);
|
|
153
|
+
const [owner, rh, eth, btc, sol, xrp, contenthash, ...texts] = results;
|
|
154
|
+
if (!owner || owner.status === 'failure')
|
|
155
|
+
return null;
|
|
156
|
+
const stored = (e) => e && e.status === 'success' && typeof e.result === 'string' ? e.result : '0x';
|
|
157
|
+
const records = {};
|
|
158
|
+
TEXT_KEYS.forEach((key, i) => {
|
|
159
|
+
const e = texts[i];
|
|
160
|
+
if (e && e.status === 'success' && typeof e.result === 'string' && e.result)
|
|
161
|
+
records[key] = e.result;
|
|
162
|
+
});
|
|
163
|
+
const content = decodeContenthash(stored(contenthash));
|
|
164
|
+
return {
|
|
165
|
+
name: checked.name,
|
|
166
|
+
node,
|
|
167
|
+
owner: getAddress(owner.result),
|
|
168
|
+
addresses: {
|
|
169
|
+
robinhood: CHAINS.robinhood.decode(stored(rh)),
|
|
170
|
+
ethereum: CHAINS.ethereum.decode(stored(eth)),
|
|
171
|
+
bitcoin: CHAINS.bitcoin.decode(stored(btc)),
|
|
172
|
+
solana: CHAINS.solana.decode(stored(sol)),
|
|
173
|
+
xrp: CHAINS.xrp.decode(stored(xrp)),
|
|
174
|
+
},
|
|
175
|
+
records,
|
|
176
|
+
website: content
|
|
177
|
+
? { ...content, url: `https://${checked.name}.link/`, gateway: contentGatewayUrl(content) }
|
|
178
|
+
: null,
|
|
179
|
+
explorer: `${ROBINHOOD_EXPLORER}/token/${r}/instance/${BigInt(node).toString()}`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The address to pay `name` at on `chain`, or a HoodfiError.
|
|
184
|
+
*
|
|
185
|
+
* Reads that chain's own record and nothing else. An empty Robinhood Chain record is
|
|
186
|
+
* NOT filled from the Ethereum one (or vice versa): a contract wallet like a Safe only
|
|
187
|
+
* exists on chains it was deployed to, and funds sent to its address elsewhere are
|
|
188
|
+
* stuck. Empty means ask the recipient.
|
|
189
|
+
*/
|
|
190
|
+
async function getSendAddress(name, chain) {
|
|
191
|
+
const spec = CHAINS[chain];
|
|
192
|
+
if (!spec)
|
|
193
|
+
throw new HoodfiError('INVALID_NAME', `Unknown chain "${String(chain)}".`);
|
|
194
|
+
const profile = await resolve(name);
|
|
195
|
+
if (!profile)
|
|
196
|
+
throw new HoodfiError('NOT_REGISTERED', `${requireName(name).name} is not registered.`);
|
|
197
|
+
const address = profile.addresses[chain];
|
|
198
|
+
const sibling = chain === 'robinhood' ? 'ethereum' : chain === 'ethereum' ? 'robinhood' : null;
|
|
199
|
+
const other = sibling ? profile.addresses[sibling] : null;
|
|
200
|
+
if (!address)
|
|
201
|
+
throw new HoodfiError('NO_RECORD', `${profile.name} has no ${spec.label} address set, so it can't be paid there. Don't send to one of its other addresses instead.` +
|
|
202
|
+
(other && sibling ? ` It does have a ${CHAINS[sibling].label} address; the owner should set both.` : ''));
|
|
203
|
+
const warnings = [];
|
|
204
|
+
if (other && sibling && other !== address)
|
|
205
|
+
warnings.push(`${profile.name}'s two ETH records disagree: ${spec.label} → ${address}, ${CHAINS[sibling].label} → ${other}. This uses the ${spec.label} one; confirm with the recipient.`);
|
|
206
|
+
return { name: profile.name, chain, address, warnings, caveat: paymentCaveat(chain, address) };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The hoodfi names an address holds, and which one to display for it.
|
|
210
|
+
*
|
|
211
|
+
* Served by the HoodFi gateway (it indexes Transfer logs; the registry isn't
|
|
212
|
+
* enumerable), cached ~60s. Throws UNREADABLE on a failed read: an empty `names` list
|
|
213
|
+
* really means none.
|
|
214
|
+
*/
|
|
215
|
+
async function lookupAddress(address) {
|
|
216
|
+
const checksummed = requireAddress(address);
|
|
217
|
+
let response;
|
|
218
|
+
try {
|
|
219
|
+
response = await doFetch(`${apiUrl}/owner/${checksummed}/names`, { headers: { accept: 'application/json' } });
|
|
220
|
+
}
|
|
221
|
+
catch (cause) {
|
|
222
|
+
throw new HoodfiError('UNREADABLE', 'Could not reach the HoodFi API. Try again shortly.', { cause });
|
|
223
|
+
}
|
|
224
|
+
if (!response.ok)
|
|
225
|
+
throw new HoodfiError('UNREADABLE', `The HoodFi API answered ${response.status}. This says nothing about the address; try again shortly.`);
|
|
226
|
+
return (await response.json());
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Unsigned transactions that register `name` for `account`.
|
|
230
|
+
*
|
|
231
|
+
* The registrar mints to whoever SENDS the transaction: there is no recipient
|
|
232
|
+
* argument. Sign with `account` or the name goes to the actual signer.
|
|
233
|
+
*
|
|
234
|
+
* `payWith: 'usdg'` adds an approve step when the allowance is short. `overpayBps`
|
|
235
|
+
* (ETH only, default 100 = 1%) covers the owner repricing between quote and send;
|
|
236
|
+
* the registrar refunds anything above the price to the sender.
|
|
237
|
+
*/
|
|
238
|
+
async function buildRegistration(args) {
|
|
239
|
+
const label = requireLabel(args.name);
|
|
240
|
+
const account = requireAddress(args.account);
|
|
241
|
+
const payWith = args.payWith ?? 'eth';
|
|
242
|
+
if (payWith !== 'eth' && payWith !== 'usdg')
|
|
243
|
+
throw new HoodfiError('INVALID_NAME', 'payWith must be "eth" or "usdg".');
|
|
244
|
+
const overpayBps = args.overpayBps ?? 100;
|
|
245
|
+
if (!Number.isInteger(overpayBps) || overpayBps < 0 || overpayBps > 10_000)
|
|
246
|
+
throw new HoodfiError('INVALID_NAME', 'overpayBps must be an integer from 0 to 10000.');
|
|
247
|
+
const { status, wei, usdg, paused } = await readRegistrar(label);
|
|
248
|
+
if (paused)
|
|
249
|
+
throw new HoodfiError('UNAVAILABLE', 'Minting is paused by the registrar owner.');
|
|
250
|
+
const reason = unavailableReason(status, label);
|
|
251
|
+
if (reason)
|
|
252
|
+
throw new HoodfiError('UNAVAILABLE', reason);
|
|
253
|
+
const name = fullName(label);
|
|
254
|
+
const steps = [];
|
|
255
|
+
const warnings = [];
|
|
256
|
+
try {
|
|
257
|
+
if (payWith === 'eth') {
|
|
258
|
+
const value = wei + (wei * BigInt(overpayBps)) / 10000n;
|
|
259
|
+
const balance = await client.getBalance({ address: account });
|
|
260
|
+
if (balance < value)
|
|
261
|
+
warnings.push(`${account} holds less ETH on Robinhood Chain than the ${value} wei this sends.`);
|
|
262
|
+
steps.push({
|
|
263
|
+
description: `Register ${name}`,
|
|
264
|
+
to: CONTRACTS.registrar,
|
|
265
|
+
data: encodeFunctionData({ abi: registrarAbi, functionName: 'register', args: [label] }),
|
|
266
|
+
value,
|
|
267
|
+
chainId: ROBINHOOD_CHAIN_ID,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
const [allowance, balance] = await client.multicall({
|
|
272
|
+
contracts: [
|
|
273
|
+
{ address: CONTRACTS.usdg, abi: erc20Abi, functionName: 'allowance', args: [account, CONTRACTS.registrar] },
|
|
274
|
+
{ address: CONTRACTS.usdg, abi: erc20Abi, functionName: 'balanceOf', args: [account] },
|
|
275
|
+
],
|
|
276
|
+
allowFailure: false,
|
|
277
|
+
});
|
|
278
|
+
if (balance < usdg)
|
|
279
|
+
warnings.push(`${account} holds less USDG than the price.`);
|
|
280
|
+
if (allowance < usdg)
|
|
281
|
+
steps.push({
|
|
282
|
+
description: 'Approve the registrar to spend the USDG price',
|
|
283
|
+
to: CONTRACTS.usdg,
|
|
284
|
+
data: encodeFunctionData({ abi: erc20Abi, functionName: 'approve', args: [CONTRACTS.registrar, usdg] }),
|
|
285
|
+
value: 0n,
|
|
286
|
+
chainId: ROBINHOOD_CHAIN_ID,
|
|
287
|
+
});
|
|
288
|
+
steps.push({
|
|
289
|
+
description: `Register ${name}`,
|
|
290
|
+
to: CONTRACTS.registrar,
|
|
291
|
+
data: encodeFunctionData({ abi: registrarAbi, functionName: 'registerWithUsdc', args: [label] }),
|
|
292
|
+
value: 0n,
|
|
293
|
+
chainId: ROBINHOOD_CHAIN_ID,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (cause) {
|
|
298
|
+
throw new HoodfiError('UNREADABLE', UNREADABLE_MSG, { cause });
|
|
299
|
+
}
|
|
300
|
+
return { name, label, payWith, price: { wei, usdg }, steps, warnings };
|
|
301
|
+
}
|
|
302
|
+
return { checkName, resolve, getSendAddress, lookupAddress, buildRegistration, publicClient: client };
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/coins.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decoding the address records a name can carry.
|
|
3
|
+
*
|
|
4
|
+
* ENSIP-9 stores an address as its chain's own bytes, not the text people copy (Bitcoin
|
|
5
|
+
* keeps its scriptPubKey, Solana its raw ed25519 key), so each record is re-derived
|
|
6
|
+
* through a per-chain codec rather than echoed. Same codecs as mcp/src/coins.ts and
|
|
7
|
+
* frontend/lib/ens.ts: `@ensdomains/address-encoder`.
|
|
8
|
+
*/
|
|
9
|
+
import { type Hex } from 'viem';
|
|
10
|
+
/** Where a payment goes. `robinhood` and `ethereum` are separate records on purpose. */
|
|
11
|
+
export type SendChain = 'robinhood' | 'ethereum' | 'bitcoin' | 'solana' | 'xrp';
|
|
12
|
+
export type Verification = 'checksummed' | 'if-mixed-case' | 'length-only';
|
|
13
|
+
interface Chain {
|
|
14
|
+
label: string;
|
|
15
|
+
symbol: string;
|
|
16
|
+
coinType: bigint;
|
|
17
|
+
/**
|
|
18
|
+
* How much a well-formed record proves. Bitcoin and XRP carry a checksum. An EVM
|
|
19
|
+
* record is checksummed when written in EIP-55 case, which this SDK always returns.
|
|
20
|
+
* Solana has none: any 32 bytes is a valid-looking key, so a typo at write time is
|
|
21
|
+
* undetectable at read time.
|
|
22
|
+
*/
|
|
23
|
+
verification: Verification;
|
|
24
|
+
decode(stored: Hex): string | null;
|
|
25
|
+
}
|
|
26
|
+
export declare const CHAINS: Record<SendChain, Chain>;
|
|
27
|
+
export declare const SEND_CHAINS: SendChain[];
|
|
28
|
+
/**
|
|
29
|
+
* What to tell a user before they pay this address, or null when the record itself
|
|
30
|
+
* proves it is well-formed. Relay it; don't report the address as "validated".
|
|
31
|
+
*/
|
|
32
|
+
export declare function paymentCaveat(chain: SendChain, address: string): string | null;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=coins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coins.d.ts","sourceRoot":"","sources":["../src/coins.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,KAAK,GAAG,EAA0B,MAAM,MAAM,CAAA;AAKvD,wFAAwF;AACxF,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,KAAK,CAAA;AAE/E,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,eAAe,GAAG,aAAa,CAAA;AAE1E,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,YAAY,EAAE,YAAY,CAAA;IAC1B,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAAA;CACnC;AAuBD,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,CAM3C,CAAA;AAED,eAAO,MAAM,WAAW,EAA0B,SAAS,EAAE,CAAA;AAE7D;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM9E"}
|
package/dist/coins.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decoding the address records a name can carry.
|
|
3
|
+
*
|
|
4
|
+
* ENSIP-9 stores an address as its chain's own bytes, not the text people copy (Bitcoin
|
|
5
|
+
* keeps its scriptPubKey, Solana its raw ed25519 key), so each record is re-derived
|
|
6
|
+
* through a per-chain codec rather than echoed. Same codecs as mcp/src/coins.ts and
|
|
7
|
+
* frontend/lib/ens.ts: `@ensdomains/address-encoder`.
|
|
8
|
+
*/
|
|
9
|
+
import { encodeBtcAddress, encodeSolAddress } from '@ensdomains/address-encoder/coders';
|
|
10
|
+
import { getAddress, hexToBytes } from 'viem';
|
|
11
|
+
import { BTC_COIN_TYPE, ETH_COIN_TYPE, ROBINHOOD_COIN_TYPE, SOL_COIN_TYPE, XRP_COIN_TYPE } from './constants.js';
|
|
12
|
+
import { encodeXrpRecord } from './xrp.js';
|
|
13
|
+
const ZERO = '0x0000000000000000000000000000000000000000';
|
|
14
|
+
function evm(stored) {
|
|
15
|
+
if (!stored || stored.length !== 42)
|
|
16
|
+
return null;
|
|
17
|
+
const address = getAddress(stored);
|
|
18
|
+
return address === ZERO ? null : address;
|
|
19
|
+
}
|
|
20
|
+
function via(toText) {
|
|
21
|
+
return (stored) => {
|
|
22
|
+
if (!stored || stored === '0x')
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
return toText(hexToBytes(stored));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// The library names directions from the address text's side: `encode…` turns stored
|
|
33
|
+
// bytes INTO text, which is the only direction a reader needs.
|
|
34
|
+
export const CHAINS = {
|
|
35
|
+
robinhood: { label: 'Robinhood Chain', symbol: 'ETH', coinType: ROBINHOOD_COIN_TYPE, verification: 'if-mixed-case', decode: evm },
|
|
36
|
+
ethereum: { label: 'Ethereum', symbol: 'ETH', coinType: ETH_COIN_TYPE, verification: 'if-mixed-case', decode: evm },
|
|
37
|
+
bitcoin: { label: 'Bitcoin', symbol: 'BTC', coinType: BTC_COIN_TYPE, verification: 'checksummed', decode: via(encodeBtcAddress) },
|
|
38
|
+
solana: { label: 'Solana', symbol: 'SOL', coinType: SOL_COIN_TYPE, verification: 'length-only', decode: via(encodeSolAddress) },
|
|
39
|
+
xrp: { label: 'XRP Ledger', symbol: 'XRP', coinType: XRP_COIN_TYPE, verification: 'checksummed', decode: via(encodeXrpRecord) },
|
|
40
|
+
};
|
|
41
|
+
export const SEND_CHAINS = Object.keys(CHAINS);
|
|
42
|
+
/**
|
|
43
|
+
* What to tell a user before they pay this address, or null when the record itself
|
|
44
|
+
* proves it is well-formed. Relay it; don't report the address as "validated".
|
|
45
|
+
*/
|
|
46
|
+
export function paymentCaveat(chain, address) {
|
|
47
|
+
if (chain === 'xrp' && address.startsWith('r'))
|
|
48
|
+
return 'Classic XRP Ledger address (r…): it carries no destination tag. Fine for a personal wallet; an exchange deposit sent without its tag can be lost.';
|
|
49
|
+
if (chain === 'solana')
|
|
50
|
+
return 'Solana addresses carry no checksum, so a mistyped record looks exactly like a valid one. Confirm it with the recipient before a large payment.';
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=coins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coins.js","sourceRoot":"","sources":["../src/coins.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAA;AACvF,OAAO,EAAY,UAAU,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,mBAAmB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAChH,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAqB1C,MAAM,IAAI,GAAG,4CAA4C,CAAA;AAEzD,SAAS,GAAG,CAAC,MAAW;IACtB,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAChD,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;IAClC,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAA;AAC1C,CAAC;AAED,SAAS,GAAG,CAAC,MAAqC;IAChD,OAAO,CAAC,MAAW,EAAiB,EAAE;QACpC,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,IAAI,CAAA;QAC3C,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,oFAAoF;AACpF,+DAA+D;AAC/D,MAAM,CAAC,MAAM,MAAM,GAA6B;IAC9C,SAAS,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,EAAE;IACjI,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,EAAE;IACnH,OAAO,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,CAAC,gBAAgB,CAAC,EAAE;IACjI,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,CAAC,gBAAgB,CAAC,EAAE;IAC/H,GAAG,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,CAAC,eAAe,CAAC,EAAE;CAChI,CAAA;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAgB,CAAA;AAE7D;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAgB,EAAE,OAAe;IAC7D,IAAI,KAAK,KAAK,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;QAC5C,OAAO,mJAAmJ,CAAA;IAC5J,IAAI,KAAK,KAAK,QAAQ;QACpB,OAAO,gJAAgJ,CAAA;IACzJ,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Chain } from 'viem';
|
|
2
|
+
export declare const PARENT = "hoodfi.eth";
|
|
3
|
+
export declare const ROBINHOOD_CHAIN_ID = 4663;
|
|
4
|
+
export declare const DEFAULT_ROBINHOOD_RPC = "https://rpc.mainnet.chain.robinhood.com";
|
|
5
|
+
/** Used when the public RPC rate-limits. It's throttled too, but on a separate budget. */
|
|
6
|
+
export declare const FALLBACK_ROBINHOOD_RPC = "https://robinhoodchain.blockscout.com/api/eth-rpc";
|
|
7
|
+
export declare const ROBINHOOD_EXPLORER = "https://robinhoodchain.blockscout.com";
|
|
8
|
+
/** Base URL of the HoodFi gateway that serves the reverse lookup. */
|
|
9
|
+
export declare const DEFAULT_API_URL = "https://ccip.hoodfi-mcp.com";
|
|
10
|
+
/** Live contracts. Owners and sources: contracts/README.md in the repository. */
|
|
11
|
+
export declare const CONTRACTS: {
|
|
12
|
+
readonly registry: "0xf2bABA012244bdD7445129597350054E1B3aEe5C";
|
|
13
|
+
readonly registrar: "0x56be5565acc823f4195c2cf3b9046C083633209a";
|
|
14
|
+
readonly partnerRouter: "0xb7bd5f2c7c445ddc5d55ebd9b8f6a20c2e6e4b5d";
|
|
15
|
+
readonly usdg: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168";
|
|
16
|
+
/** HoodfiL1Resolver on Ethereum mainnet. */
|
|
17
|
+
readonly l1Resolver: "0x37215Dd89D0Fd4ea0Dbce690bDe58490fB7f7cF2";
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The ENSIP-11 coinType for Robinhood Chain, 0x80000000 | 4663.
|
|
21
|
+
*
|
|
22
|
+
* It's written as a literal on purpose. In JS, `0x80000000 | 4663` is a signed 32-bit
|
|
23
|
+
* operation and comes out negative, so every addr read silently returns nothing.
|
|
24
|
+
*/
|
|
25
|
+
export declare const ROBINHOOD_COIN_TYPE = 2147488311n;
|
|
26
|
+
export declare const ETH_COIN_TYPE = 60n;
|
|
27
|
+
export declare const BTC_COIN_TYPE = 0n;
|
|
28
|
+
export declare const SOL_COIN_TYPE = 501n;
|
|
29
|
+
export declare const XRP_COIN_TYPE = 144n;
|
|
30
|
+
export declare const robinhoodChain: Chain;
|
|
31
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAe,MAAM,MAAM,CAAA;AAE9C,eAAO,MAAM,MAAM,eAAe,CAAA;AAClC,eAAO,MAAM,kBAAkB,OAAO,CAAA;AACtC,eAAO,MAAM,qBAAqB,4CAA4C,CAAA;AAC9E,0FAA0F;AAC1F,eAAO,MAAM,sBAAsB,sDAAsD,CAAA;AACzF,eAAO,MAAM,kBAAkB,0CAA0C,CAAA;AACzE,qEAAqE;AACrE,eAAO,MAAM,eAAe,gCAAgC,CAAA;AAE5D,iFAAiF;AACjF,eAAO,MAAM,SAAS;;;;;IAKpB,4CAA4C;;CAEpC,CAAA;AAEV;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,cAAc,CAAA;AAC9C,eAAO,MAAM,aAAa,MAAM,CAAA;AAChC,eAAO,MAAM,aAAa,KAAK,CAAA;AAC/B,eAAO,MAAM,aAAa,OAAO,CAAA;AACjC,eAAO,MAAM,aAAa,OAAO,CAAA;AAEjC,eAAO,MAAM,cAAc,EAAE,KAO3B,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineChain } from 'viem';
|
|
2
|
+
export const PARENT = 'hoodfi.eth';
|
|
3
|
+
export const ROBINHOOD_CHAIN_ID = 4663;
|
|
4
|
+
export const DEFAULT_ROBINHOOD_RPC = 'https://rpc.mainnet.chain.robinhood.com';
|
|
5
|
+
/** Used when the public RPC rate-limits. It's throttled too, but on a separate budget. */
|
|
6
|
+
export const FALLBACK_ROBINHOOD_RPC = 'https://robinhoodchain.blockscout.com/api/eth-rpc';
|
|
7
|
+
export const ROBINHOOD_EXPLORER = 'https://robinhoodchain.blockscout.com';
|
|
8
|
+
/** Base URL of the HoodFi gateway that serves the reverse lookup. */
|
|
9
|
+
export const DEFAULT_API_URL = 'https://ccip.hoodfi-mcp.com';
|
|
10
|
+
/** Live contracts. Owners and sources: contracts/README.md in the repository. */
|
|
11
|
+
export const CONTRACTS = {
|
|
12
|
+
registry: '0xf2bABA012244bdD7445129597350054E1B3aEe5C',
|
|
13
|
+
registrar: '0x56be5565acc823f4195c2cf3b9046C083633209a',
|
|
14
|
+
partnerRouter: '0xb7bd5f2c7c445ddc5d55ebd9b8f6a20c2e6e4b5d',
|
|
15
|
+
usdg: '0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168',
|
|
16
|
+
/** HoodfiL1Resolver on Ethereum mainnet. */
|
|
17
|
+
l1Resolver: '0x37215Dd89D0Fd4ea0Dbce690bDe58490fB7f7cF2',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The ENSIP-11 coinType for Robinhood Chain, 0x80000000 | 4663.
|
|
21
|
+
*
|
|
22
|
+
* It's written as a literal on purpose. In JS, `0x80000000 | 4663` is a signed 32-bit
|
|
23
|
+
* operation and comes out negative, so every addr read silently returns nothing.
|
|
24
|
+
*/
|
|
25
|
+
export const ROBINHOOD_COIN_TYPE = 2147488311n;
|
|
26
|
+
export const ETH_COIN_TYPE = 60n;
|
|
27
|
+
export const BTC_COIN_TYPE = 0n;
|
|
28
|
+
export const SOL_COIN_TYPE = 501n;
|
|
29
|
+
export const XRP_COIN_TYPE = 144n;
|
|
30
|
+
export const robinhoodChain = defineChain({
|
|
31
|
+
id: ROBINHOOD_CHAIN_ID,
|
|
32
|
+
name: 'Robinhood Chain',
|
|
33
|
+
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
|
34
|
+
rpcUrls: { default: { http: [DEFAULT_ROBINHOOD_RPC] } },
|
|
35
|
+
blockExplorers: { default: { name: 'Blockscout', url: ROBINHOOD_EXPLORER } },
|
|
36
|
+
contracts: { multicall3: { address: '0xcA11bde05977b3631167028862bE2a173976CA11' } },
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,WAAW,EAAE,MAAM,MAAM,CAAA;AAE9C,MAAM,CAAC,MAAM,MAAM,GAAG,YAAY,CAAA;AAClC,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAA;AACtC,MAAM,CAAC,MAAM,qBAAqB,GAAG,yCAAyC,CAAA;AAC9E,0FAA0F;AAC1F,MAAM,CAAC,MAAM,sBAAsB,GAAG,mDAAmD,CAAA;AACzF,MAAM,CAAC,MAAM,kBAAkB,GAAG,uCAAuC,CAAA;AACzE,qEAAqE;AACrE,MAAM,CAAC,MAAM,eAAe,GAAG,6BAA6B,CAAA;AAE5D,iFAAiF;AACjF,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,QAAQ,EAAE,4CAA4C;IACtD,SAAS,EAAE,4CAA4C;IACvD,aAAa,EAAE,4CAA4C;IAC3D,IAAI,EAAE,4CAA4C;IAClD,4CAA4C;IAC5C,UAAU,EAAE,4CAA4C;CAChD,CAAA;AAEV;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,CAAA;AAC9C,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAA;AAChC,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAA;AAC/B,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAA;AACjC,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAA;AAEjC,MAAM,CAAC,MAAM,cAAc,GAAU,WAAW,CAAC;IAC/C,EAAE,EAAE,kBAAkB;IACtB,IAAI,EAAE,iBAAiB;IACvB,cAAc,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE;IAC9D,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,qBAAqB,CAAC,EAAE,EAAE;IACvD,cAAc,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,kBAAkB,EAAE,EAAE;IAC5E,SAAS,EAAE,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,4CAA4C,EAAE,EAAE;CACrF,CAAC,CAAA"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EIP-1577 contenthash — the record that turns a name into a website.
|
|
3
|
+
*
|
|
4
|
+
* Only the IPFS family is handled, which is the whole of what this site can honestly
|
|
5
|
+
* offer: names are minted here, pinned here and served through IPFS gateways here.
|
|
6
|
+
* Swarm and Arweave namespaces exist in the spec and are deliberately not accepted —
|
|
7
|
+
* writing one would produce a record we can't preview, can't link to, and can't tell
|
|
8
|
+
* an owner is working.
|
|
9
|
+
*
|
|
10
|
+
* The stored bytes are <protoCode varint><CID>, so a record is two decisions: which
|
|
11
|
+
* namespace, and a CID the gateways will accept.
|
|
12
|
+
*
|
|
13
|
+
* ## One copy, imported by three packages
|
|
14
|
+
*
|
|
15
|
+
* The site writes records on `/manage` and reads them on `/search`; the MCP worker
|
|
16
|
+
* (`mcp/src/tools.ts`) does both for agents; the gateway's share page is next. Two
|
|
17
|
+
* implementations of a byte format is the thing to avoid above all else here — a
|
|
18
|
+
* contenthash off by one byte is a *well-formed* record that resolves to nothing, on a
|
|
19
|
+
* name whose owner has no way to tell it is broken. So there is exactly one copy, this
|
|
20
|
+
* is it, and the workers import it across the repo by relative path.
|
|
21
|
+
*
|
|
22
|
+
* **Why it sits under `frontend/` rather than a top-level `shared/`:** Turbopack refuses
|
|
23
|
+
* to resolve any import that leaves its project root — a `@shared/*` alias pointing at
|
|
24
|
+
* `../shared` fails the build with "aliased to relative '../shared/…' inside of
|
|
25
|
+
* [project]/" — and Vercel builds this app with `frontend/` as its Root Directory, so a
|
|
26
|
+
* sibling directory is not guaranteed to be in the build at all. The workers have no
|
|
27
|
+
* such restriction: esbuild follows a relative path anywhere. So the file lives where
|
|
28
|
+
* the fussiest consumer can see it, and `shared/` names it for the others.
|
|
29
|
+
*
|
|
30
|
+
* **It has no bare imports on purpose.** Whoever bundles it resolves `node_modules` by
|
|
31
|
+
* walking up from *this* file, which would silently tie the workers to whatever version
|
|
32
|
+
* the site happens to have installed — and break them entirely if the site's install is
|
|
33
|
+
* missing. Everything needed is small, pure and specified: hex, base32, base58 and
|
|
34
|
+
* base36 come to ~60 lines, they are checked vector-by-vector against `multiformats` in
|
|
35
|
+
* `contenthash.test.mjs`, and being dependency-free is what lets that test run under
|
|
36
|
+
* plain `node` with nothing installed.
|
|
37
|
+
*
|
|
38
|
+
* It must also stay clean under `noUncheckedIndexedAccess`, which `mcp/` sets and the
|
|
39
|
+
* site does not. Reasonable for a parser: every index here is into attacker-supplied
|
|
40
|
+
* input.
|
|
41
|
+
*/
|
|
42
|
+
export type ContentProtocol = "ipfs" | "ipns";
|
|
43
|
+
export type ContentHash = {
|
|
44
|
+
protocol: ContentProtocol;
|
|
45
|
+
/**
|
|
46
|
+
* The canonical multibase id: base32 for an IPFS CID, base36 for an IPNS key. Both
|
|
47
|
+
* are lowercase and DNS-safe, which is what lets them be used as a gateway subdomain
|
|
48
|
+
* — the only gateway form that serves a site's own assets from the site's own origin.
|
|
49
|
+
*/
|
|
50
|
+
id: string;
|
|
51
|
+
/** `ipfs://…` — how every other ENS tool shows the same record. */
|
|
52
|
+
uri: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Reads what the registry holds. Returns null for an unset record and — deliberately —
|
|
56
|
+
* for a record in a namespace this site doesn't handle, so callers can't offer a
|
|
57
|
+
* "Visit" button pointing somewhere they were never able to check.
|
|
58
|
+
*/
|
|
59
|
+
export declare function decodeContenthash(stored: string | undefined | null): ContentHash | null;
|
|
60
|
+
/**
|
|
61
|
+
* Whatever an owner pasted -> the bytes to store, or null if it can't be one.
|
|
62
|
+
*
|
|
63
|
+
* Generous about the input because everything people copy is a different shape of the
|
|
64
|
+
* same CID: the URI form, a bare CID, a gateway link with the CID in the path, and a
|
|
65
|
+
* subdomain gateway link with it in the host. Every one of those is unambiguous, and
|
|
66
|
+
* refusing them would only teach owners to hand-edit a URL until it looks like ours.
|
|
67
|
+
*/
|
|
68
|
+
export declare function encodeContenthash(input: string): `0x${string}` | null;
|
|
69
|
+
/** The same parse, exposed for the editor's "what you're about to publish" preview. */
|
|
70
|
+
export declare function parseContenthash(input: string): ContentHash | null;
|
|
71
|
+
/**
|
|
72
|
+
* A URL that serves the site today, for anyone in an ordinary browser.
|
|
73
|
+
*
|
|
74
|
+
* Deliberately a *subdomain* gateway: a path gateway (`/ipfs/<cid>/`) serves every page
|
|
75
|
+
* from one origin, so a site's own absolute links, its styles and its scripts resolve
|
|
76
|
+
* against the gateway root and the page arrives unstyled. The subdomain form gives the
|
|
77
|
+
* CID its own origin and the site loads as its author built it.
|
|
78
|
+
*
|
|
79
|
+
* Not our own Pinata gateway, which serves only what this account pinned and 403s
|
|
80
|
+
* everything else — owners publish their sites wherever they like.
|
|
81
|
+
*/
|
|
82
|
+
export declare function contentGatewayUrl(content: ContentHash): string;
|
|
83
|
+
/**
|
|
84
|
+
* The address the name itself answers on.
|
|
85
|
+
*
|
|
86
|
+
* This is the point of setting the record — the CID link above is the fallback that
|
|
87
|
+
* always works, this is the one worth telling people.
|
|
88
|
+
*
|
|
89
|
+
* `.eth.link` and `.eth.limo` both serve a hoodfi subname (verified on gm.hoodfi.eth
|
|
90
|
+
* once the first record existed) and both are run by eth.limo, so this is a naming
|
|
91
|
+
* call rather than a technical one: `.link` reads as what it is. Kept as one function
|
|
92
|
+
* so every place that shows an owner their address and sends a visitor to it can
|
|
93
|
+
* never drift apart.
|
|
94
|
+
*/
|
|
95
|
+
export declare function nameUrl(label: string): string;
|
|
96
|
+
//# sourceMappingURL=contenthash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contenthash.d.ts","sourceRoot":"","sources":["../src/contenthash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAWH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,eAAe,CAAC;IAC1B;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAC;IACX,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AA0MF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAChC,WAAW,GAAG,IAAI,CAmBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,MAAM,EAAE,GAAG,IAAI,CAKrE;AAED,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAQlE;AA6DD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAE9D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C"}
|