@midnames/sdk 1.0.0 → 1.0.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/dist/provider.js CHANGED
@@ -1,12 +1,30 @@
1
1
  import { setNetworkId, } from "@midnight-ntwrk/midnight-js-network-id";
2
2
  import { indexerPublicDataProvider } from "@midnight-ntwrk/midnight-js-indexer-public-data-provider";
3
+ export const NETWORK_REGISTRY = {
4
+ preview: {
5
+ indexerUrl: "https://indexer.preview.midnight.network/api/v3/graphql",
6
+ indexerWsUrl: "wss://indexer.preview.midnight.network/api/v3/graphql/ws",
7
+ tldAddress: "daa9fc4dfbd42ac9227f8b4358928532da68337689ea3b21b351d607890d9192",
8
+ },
9
+ preprod: {
10
+ indexerUrl: "https://indexer.preprod.midnight.network/api/v3/graphql",
11
+ indexerWsUrl: "wss://indexer.preprod.midnight.network/api/v3/graphql/ws",
12
+ tldAddress: "b00a62ce58c108b86af4670e662fa240844e0206c54009a539c64155d62e6b6a",
13
+ },
14
+ };
15
+ export function getNetworkConfig(networkId) {
16
+ const config = NETWORK_REGISTRY[networkId];
17
+ if (!config) {
18
+ throw new Error(`Unknown network "${networkId}". Known networks: ${Object.keys(NETWORK_REGISTRY).join(", ")}. Use createDefaultProvider() with explicit URLs for custom networks.`);
19
+ }
20
+ return config;
21
+ }
3
22
  let defaultProvider = null;
4
- export function getDefaultProvider() {
23
+ export function getDefaultProvider(networkId = "preview") {
5
24
  if (!defaultProvider) {
6
- const indexerUrl = "https://indexer.preprod.midnight.network/api/v3/graphql";
7
- const indexerWsUrl = "wss://indexer.preprod.midnight.network/api/v3/graphql/ws";
8
- setNetworkId("preprod");
9
- defaultProvider = indexerPublicDataProvider(indexerUrl, indexerWsUrl);
25
+ const net = getNetworkConfig(networkId);
26
+ setNetworkId(networkId);
27
+ defaultProvider = indexerPublicDataProvider(net.indexerUrl, net.indexerWsUrl);
10
28
  }
11
29
  return defaultProvider;
12
30
  }
@@ -14,7 +32,14 @@ export function setDefaultProvider(provider) {
14
32
  defaultProvider = provider;
15
33
  }
16
34
  export function createDefaultProvider(config = {}) {
17
- const { indexerUrl = "https://indexer.preprod.midnight.network/api/v3/graphql", indexerWsUrl = "wss://indexer.preprod.midnight.network/api/v3/graphql/ws", networkId = "preprod", } = config;
35
+ var _a, _b, _c;
36
+ const networkId = (_a = config.networkId) !== null && _a !== void 0 ? _a : "preview";
37
+ const knownConfig = NETWORK_REGISTRY[networkId];
38
+ const indexerUrl = (_b = config.indexerUrl) !== null && _b !== void 0 ? _b : knownConfig === null || knownConfig === void 0 ? void 0 : knownConfig.indexerUrl;
39
+ const indexerWsUrl = (_c = config.indexerWsUrl) !== null && _c !== void 0 ? _c : knownConfig === null || knownConfig === void 0 ? void 0 : knownConfig.indexerWsUrl;
40
+ if (!indexerUrl || !indexerWsUrl) {
41
+ throw new Error(`No indexer URLs for network "${networkId}". Provide indexerUrl and indexerWsUrl explicitly.`);
42
+ }
18
43
  setNetworkId(networkId);
19
44
  return indexerPublicDataProvider(indexerUrl, indexerWsUrl);
20
45
  }
@@ -5,6 +5,7 @@ import { deriveShieldedAddress } from '../utils/address.js';
5
5
  import { Copy, Check } from 'lucide-react';
6
6
  import { getDomainProfile } from '../core.js';
7
7
  import { getDefaultProvider } from '../provider.js';
8
+ import { sanitizeUrl } from '../utils/sanitize.js';
8
9
  function truncate(addr, head = 10, tail = 6) {
9
10
  if (!addr)
10
11
  return 'N/A';
@@ -91,7 +92,9 @@ onFieldClick, onCopySuccess, }) {
91
92
  }, [publicDataProvider, fullDomain, onError]);
92
93
  const fieldsMap = useMemo(() => {
93
94
  const m = {};
94
- ((data === null || data === void 0 ? void 0 : data.fields) || []).forEach(([k, v]) => (m[k.toLowerCase()] = v));
95
+ if (data === null || data === void 0 ? void 0 : data.fields) {
96
+ data.fields.forEach((v, k) => (m[k.toLowerCase()] = v));
97
+ }
95
98
  return m;
96
99
  }, [data]);
97
100
  const displayName = fieldsMap['name'] || fieldsMap['displayname'] || fieldsMap['handle'] || '';
@@ -162,5 +165,5 @@ onFieldClick, onCopySuccess, }) {
162
165
  if (error && !loading) {
163
166
  return (_jsx("div", { className: cardClasses, children: _jsxs("div", { className: "midnames-error", children: [_jsx("div", { className: "midnames-error-title", children: "Error loading domain" }), _jsx("div", { className: "midnames-error-message", children: error.message }), _jsxs("div", { className: "midnames-error-code", children: ["Code: ", error.code] })] }) }));
164
167
  }
165
- return (_jsxs("div", { className: cardClasses, children: [showBanner && bannerUrl && _jsx("img", { src: bannerUrl, alt: "", className: "midnames-banner" }), _jsxs("div", { className: "midnames-header", children: [showAvatar && (_jsx("div", { className: "midnames-avatar", children: avatarUrl ? (_jsx("img", { src: avatarUrl, alt: "", onError: (e) => (e.currentTarget.style.display = 'none') })) : (_jsx("span", { children: (_c = (_b = (_a = (displayName || (data === null || data === void 0 ? void 0 : data.fullDomain) || '?')[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : '?' })) })), _jsxs("div", { className: "midnames-ident", children: [_jsx("div", { className: "midnames-name", children: displayName || (data === null || data === void 0 ? void 0 : data.fullDomain) || fullDomain }), variant !== 'minimal' && _jsx("div", { className: "midnames-domain", children: (data === null || data === void 0 ? void 0 : data.fullDomain) || fullDomain })] }), showStatus && (_jsx("div", { className: `midnames-status midnames-status-${status}`, children: loading ? 'Checking…' : status === 'registered' ? 'Registered' : status === 'available' ? 'Available' : status === 'error' ? 'Error' : 'Unknown' }))] }), showBio && bio && _jsx("p", { className: "midnames-bio", children: bio }), showDomainInfo && fieldsToShow.length > 0 && (_jsx("div", { className: "midnames-grid", children: fieldsToShow.map((field) => (_jsxs("div", { className: "midnames-box", children: [_jsx("div", { className: "midnames-box-label", children: field.label }), _jsx(CopyableValue, { value: field.value, className: "midnames-box-value", showCopy: showCopyButtons, onCopySuccess: onCopySuccess, onFieldClick: onFieldClick, fieldName: field.fieldName })] }, field.key))) })), showSocialLinks && (website || twitter || github || location) && (_jsxs("div", { className: "midnames-links", children: [website && (_jsx("a", { className: "midnames-chip", href: website.startsWith('http') ? website : `https://${website}`, target: "_blank", rel: "noreferrer", children: website.replace(/^https?:\/\//, '') })), twitter && (_jsxs("a", { className: "midnames-chip", href: `https://x.com/${twitter}`, target: "_blank", rel: "noreferrer", children: ["@", twitter] })), github && (_jsx("a", { className: "midnames-chip", href: `https://github.com/${github}`, target: "_blank", rel: "noreferrer", children: github })), location && _jsx("span", { className: "midnames-chip", children: location })] })), renderActions && (_jsx("div", { className: "midnames-actions", children: renderActions(data) }))] }));
168
+ return (_jsxs("div", { className: cardClasses, children: [showBanner && bannerUrl && _jsx("img", { src: sanitizeUrl(bannerUrl), alt: "", className: "midnames-banner" }), _jsxs("div", { className: "midnames-header", children: [showAvatar && (_jsx("div", { className: "midnames-avatar", children: avatarUrl ? (_jsx("img", { src: sanitizeUrl(avatarUrl), alt: "", onError: (e) => (e.currentTarget.style.display = 'none') })) : (_jsx("span", { children: (_c = (_b = (_a = (displayName || (data === null || data === void 0 ? void 0 : data.fullDomain) || '?')[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : '?' })) })), _jsxs("div", { className: "midnames-ident", children: [_jsx("div", { className: "midnames-name", children: displayName || (data === null || data === void 0 ? void 0 : data.fullDomain) || fullDomain }), variant !== 'minimal' && _jsx("div", { className: "midnames-domain", children: (data === null || data === void 0 ? void 0 : data.fullDomain) || fullDomain })] }), showStatus && (_jsx("div", { className: `midnames-status midnames-status-${status}`, children: loading ? 'Checking…' : status === 'registered' ? 'Registered' : status === 'available' ? 'Available' : status === 'error' ? 'Error' : 'Unknown' }))] }), showBio && bio && _jsx("p", { className: "midnames-bio", children: bio }), showDomainInfo && fieldsToShow.length > 0 && (_jsx("div", { className: "midnames-grid", children: fieldsToShow.map((field) => (_jsxs("div", { className: "midnames-box", children: [_jsx("div", { className: "midnames-box-label", children: field.label }), _jsx(CopyableValue, { value: field.value, className: "midnames-box-value", showCopy: showCopyButtons, onCopySuccess: onCopySuccess, onFieldClick: onFieldClick, fieldName: field.fieldName })] }, field.key))) })), showSocialLinks && (website || twitter || github || location) && (_jsxs("div", { className: "midnames-links", children: [website && (_jsx("a", { className: "midnames-chip", href: sanitizeUrl(website.startsWith('http') ? website : `https://${website}`), target: "_blank", rel: "noreferrer", children: website.replace(/^https?:\/\//, '') })), twitter && (_jsxs("a", { className: "midnames-chip", href: `https://x.com/${twitter}`, target: "_blank", rel: "noreferrer", children: ["@", twitter] })), github && (_jsx("a", { className: "midnames-chip", href: `https://github.com/${github}`, target: "_blank", rel: "noreferrer", children: github })), location && _jsx("span", { className: "midnames-chip", children: location })] })), renderActions && (_jsx("div", { className: "midnames-actions", children: renderActions(data) }))] }));
166
169
  }
@@ -3,6 +3,7 @@ import React, { useEffect, useRef, useCallback, useMemo, useState } from 'react'
3
3
  import { MidnamesError } from '../errors.js';
4
4
  import { getDomainProfile } from '../core.js';
5
5
  import { getDefaultProvider } from '../provider.js';
6
+ import { sanitizeUrl } from '../utils/sanitize.js';
6
7
  const DEFAULT_BEHIND_GRADIENT = 'radial-gradient(farthest-side circle at var(--midnames-holo-pointer-x) var(--midnames-holo-pointer-y),hsla(266,100%,90%,var(--midnames-holo-card-opacity)) 4%,hsla(266,50%,80%,calc(var(--midnames-holo-card-opacity)*0.75)) 10%,hsla(266,25%,70%,calc(var(--midnames-holo-card-opacity)*0.5)) 50%,hsla(266,0%,60%,0) 100%),radial-gradient(35% 52% at 55% 20%,#00ffaac4 0%,#073aff00 100%),radial-gradient(100% 100% at 50% 50%,#00c1ffff 1%,#073aff00 76%),conic-gradient(from 124deg at 50% 50%,#c137ffff 0%,#07c6ffff 40%,#07c6ffff 60%,#c137ffff 100%)';
7
8
  const DEFAULT_INNER_GRADIENT = 'linear-gradient(145deg,#60496e8c 0%,#71C4FF44 100%)';
8
9
  const ANIMATION_CONFIG = {
@@ -61,7 +62,9 @@ const HolographicCardComponent = ({ domain, publicDataProvider, behindGradient,
61
62
  // Extract fields from domain data
62
63
  const fieldsMap = useMemo(() => {
63
64
  const m = {};
64
- ((data === null || data === void 0 ? void 0 : data.fields) || []).forEach(([k, v]) => (m[k.toLowerCase()] = v));
65
+ if (data === null || data === void 0 ? void 0 : data.fields) {
66
+ data.fields.forEach((v, k) => (m[k.toLowerCase()] = v));
67
+ }
65
68
  return m;
66
69
  }, [data]);
67
70
  const displayName = fieldsMap['name'] || fieldsMap['displayname'] || fieldsMap['handle'] || '';
@@ -186,7 +189,7 @@ const HolographicCardComponent = ({ domain, publicDataProvider, behindGradient,
186
189
  '--midnames-holo-behind-gradient': showBehindGradient ? (behindGradient !== null && behindGradient !== void 0 ? behindGradient : DEFAULT_BEHIND_GRADIENT) : 'none',
187
190
  '--midnames-holo-inner-gradient': innerGradient !== null && innerGradient !== void 0 ? innerGradient : DEFAULT_INNER_GRADIENT,
188
191
  }), [showBehindGradient, behindGradient, innerGradient]);
189
- return (_jsx("div", { ref: wrapRef, className: `midnames-holo-card-wrapper ${className}`.trim(), style: cardStyle, children: _jsx("section", { ref: cardRef, className: "midnames-holo-card", children: _jsxs("div", { className: "midnames-holo-inside", children: [_jsx("div", { className: "midnames-holo-shine" }), _jsx("div", { className: "midnames-holo-glare" }), _jsxs("div", { className: "midnames-holo-content midnames-holo-avatar-content", children: [loading && (_jsx("div", { className: "midnames-holo-loading", children: "Loading domain..." })), error && !loading && (_jsxs("div", { className: "midnames-holo-error", children: [_jsx("div", { children: "Failed to load domain" }), _jsx("div", { style: { fontSize: '12px', opacity: 0.8, marginTop: '8px' }, children: error.message })] })), avatarUrl && !loading && !error && (_jsx("img", { className: "midnames-holo-avatar", src: avatarUrl, alt: `${displayName || domain} avatar`, loading: "lazy", onError: (e) => { e.target.style.display = 'none'; } })), showUserInfo && !loading && !error && (displayName || handle || contactText) && (_jsxs("div", { className: "midnames-holo-user-info", children: [_jsxs("div", { className: "midnames-holo-user-details", children: [_jsx("div", { className: "midnames-holo-mini-avatar", children: _jsx("img", { src: avatarUrl || '', alt: "mini", loading: "lazy", onError: (e) => { e.target.style.display = 'none'; } }) }), _jsxs("div", { className: "midnames-holo-user-text", children: [displayName && _jsx("div", { className: "midnames-holo-handle", children: displayName }), handle && _jsxs("div", { className: "midnames-holo-status", children: ["@", handle] }), _jsx("div", { className: "midnames-holo-status", children: domain })] })] }), _jsx("button", { className: "midnames-holo-contact-btn", onClick: onContactClick, type: "button", children: contactText })] }))] })] }) }) }));
192
+ return (_jsx("div", { ref: wrapRef, className: `midnames-holo-card-wrapper ${className}`.trim(), style: cardStyle, children: _jsx("section", { ref: cardRef, className: "midnames-holo-card", children: _jsxs("div", { className: "midnames-holo-inside", children: [_jsx("div", { className: "midnames-holo-shine" }), _jsx("div", { className: "midnames-holo-glare" }), _jsxs("div", { className: "midnames-holo-content midnames-holo-avatar-content", children: [loading && (_jsx("div", { className: "midnames-holo-loading", children: "Loading domain..." })), error && !loading && (_jsxs("div", { className: "midnames-holo-error", children: [_jsx("div", { children: "Failed to load domain" }), _jsx("div", { style: { fontSize: '12px', opacity: 0.8, marginTop: '8px' }, children: error.message })] })), avatarUrl && !loading && !error && (_jsx("img", { className: "midnames-holo-avatar", src: sanitizeUrl(avatarUrl), alt: `${displayName || domain} avatar`, loading: "lazy", onError: (e) => { e.target.style.display = 'none'; } })), showUserInfo && !loading && !error && (displayName || handle || contactText) && (_jsxs("div", { className: "midnames-holo-user-info", children: [_jsxs("div", { className: "midnames-holo-user-details", children: [_jsx("div", { className: "midnames-holo-mini-avatar", children: _jsx("img", { src: sanitizeUrl(avatarUrl), alt: "mini", loading: "lazy", onError: (e) => { e.target.style.display = 'none'; } }) }), _jsxs("div", { className: "midnames-holo-user-text", children: [displayName && _jsx("div", { className: "midnames-holo-handle", children: displayName }), handle && _jsxs("div", { className: "midnames-holo-status", children: ["@", handle] }), _jsx("div", { className: "midnames-holo-status", children: domain })] })] }), _jsx("button", { className: "midnames-holo-contact-btn", onClick: onContactClick, type: "button", children: contactText })] }))] })] }) }) }));
190
193
  };
191
194
  const HolographicCard = React.memo(HolographicCardComponent);
192
195
  export default HolographicCard;
@@ -1,3 +1,3 @@
1
- export * from './DomainProfileWidget.js';
1
+ export { DomainProfileWidget } from './DomainProfileWidget.js';
2
+ export type { DomainProfileWidgetProps } from './DomainProfileWidget.js';
2
3
  export { default as HolographicCard } from './HolographicCard.js';
3
- export { CopyableValue } from './DomainProfileWidget.js';
@@ -1,3 +1,2 @@
1
- export * from './DomainProfileWidget.js';
1
+ export { DomainProfileWidget } from './DomainProfileWidget.js';
2
2
  export { default as HolographicCard } from './HolographicCard.js';
3
- export { CopyableValue } from './DomainProfileWidget.js';
package/dist/types.d.ts CHANGED
@@ -6,7 +6,11 @@ export interface DomainInfo {
6
6
  }
7
7
  export interface DomainSettings {
8
8
  coinColor: Uint8Array;
9
- domainCost: bigint;
9
+ costs: {
10
+ short: bigint;
11
+ medium: bigint;
12
+ long: bigint;
13
+ };
10
14
  }
11
15
  export interface DomainProfileData {
12
16
  fullDomain: string;
@@ -14,3 +14,8 @@ export declare function getParentDomain(fullDomain: string): string | null;
14
14
  export declare function getSubdomain(fullDomain: string): string;
15
15
  export declare function isTLD(domain: string, tld?: string): boolean;
16
16
  export declare function buildFullDomain(subdomain: string, parent: string): string;
17
+ export declare function domainToKey(name: string): {
18
+ key: Uint8Array;
19
+ len: bigint;
20
+ };
21
+ export declare function keyToDomain(key: Uint8Array): string;
@@ -55,3 +55,21 @@ export function buildFullDomain(subdomain, parent) {
55
55
  return parent;
56
56
  return `${subdomain}.${parent}`;
57
57
  }
58
+ export function domainToKey(name) {
59
+ const bytes = new TextEncoder().encode(name);
60
+ if (bytes.length === 0 || bytes.length > 32)
61
+ throw new Error(`Domain name must be 1-32 bytes, got ${bytes.length}`);
62
+ const key = new Uint8Array(32).fill(255);
63
+ key.set(bytes);
64
+ return { key, len: BigInt(bytes.length) };
65
+ }
66
+ export function keyToDomain(key) {
67
+ let len = 32;
68
+ for (let i = 0; i < 32; i++) {
69
+ if (key[i] === 255) {
70
+ len = i;
71
+ break;
72
+ }
73
+ }
74
+ return new TextDecoder().decode(key.subarray(0, len));
75
+ }
@@ -27,37 +27,37 @@ function isValidIPFSCid(cid) {
27
27
  * @returns Promise resolving to gateway URL
28
28
  */
29
29
  async function resolveIPFS(cid) {
30
- return new Promise(async (resolve) => {
31
- let resolved = false;
32
- let successCount = 0;
33
- let totalGateways = DEFAULT_IPFS_GATEWAYS.length;
34
- // Test each gateway concurrently
35
- DEFAULT_IPFS_GATEWAYS.forEach(async (gateway) => {
30
+ const controllers = DEFAULT_IPFS_GATEWAYS.map(() => new AbortController());
31
+ // Race all gateways — first successful response wins
32
+ const racePromise = new Promise((resolve, reject) => {
33
+ let pending = DEFAULT_IPFS_GATEWAYS.length;
34
+ DEFAULT_IPFS_GATEWAYS.forEach(async (gateway, i) => {
35
+ const timeoutId = setTimeout(() => controllers[i].abort(), 3000);
36
36
  try {
37
- const controller = new AbortController();
38
- const timeoutId = setTimeout(() => controller.abort(), 3000);
39
37
  const res = await fetch(`${gateway}/${cid}`, {
40
38
  method: "HEAD",
41
- signal: controller.signal,
39
+ signal: controllers[i].signal,
42
40
  });
43
41
  clearTimeout(timeoutId);
44
- if (res.ok && !resolved) {
45
- resolved = true;
42
+ if (res.ok) {
43
+ controllers.forEach((c) => c.abort());
46
44
  resolve(`${gateway}/${cid}`);
47
45
  return;
48
46
  }
49
47
  }
50
- catch (error) {
51
- // Gateway failed, continue to next
52
- }
53
- successCount++;
54
- // If all failed, use fallback
55
- if (successCount === totalGateways && !resolved) {
56
- resolved = true;
57
- resolve(`${DEFAULT_IPFS_GATEWAYS[0]}/${cid}`);
48
+ catch {
49
+ clearTimeout(timeoutId);
58
50
  }
51
+ if (--pending === 0)
52
+ reject();
59
53
  });
60
54
  });
55
+ try {
56
+ return await racePromise;
57
+ }
58
+ catch {
59
+ return `${DEFAULT_IPFS_GATEWAYS[0]}/${cid}`;
60
+ }
61
61
  }
62
62
  /**
63
63
  * Resolves a URL to an accessible HTTP(S) URL
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Sanitizes a URL to prevent javascript: and other dangerous protocols.
3
+ * Returns empty string for disallowed protocols.
4
+ * Decodes URL-encoded characters before checking to prevent bypasses like java%73cript:
5
+ */
6
+ export declare function sanitizeUrl(url: string | undefined | null): string;
@@ -0,0 +1,28 @@
1
+ const ALLOWED_PROTOCOLS = /^(https?:\/\/|ipfs:\/\/|data:image\/)/i;
2
+ /**
3
+ * Sanitizes a URL to prevent javascript: and other dangerous protocols.
4
+ * Returns empty string for disallowed protocols.
5
+ * Decodes URL-encoded characters before checking to prevent bypasses like java%73cript:
6
+ */
7
+ export function sanitizeUrl(url) {
8
+ if (!url)
9
+ return '';
10
+ const trimmed = url.trim();
11
+ if (!trimmed)
12
+ return '';
13
+ // Decode URL-encoded characters to catch bypasses like java%73cript:
14
+ let decoded;
15
+ try {
16
+ decoded = decodeURIComponent(trimmed);
17
+ }
18
+ catch {
19
+ decoded = trimmed;
20
+ }
21
+ // Relative URLs and protocol-relative are fine
22
+ if (decoded.startsWith('/') || decoded.startsWith('./'))
23
+ return trimmed;
24
+ // Allow only safe protocols (check decoded version)
25
+ if (ALLOWED_PROTOCOLS.test(decoded))
26
+ return trimmed;
27
+ return '';
28
+ }
package/managed/index.js CHANGED
@@ -1,16 +1,4 @@
1
- /* export * as Registry from "./managed/registry/contract/index.cjs";
2
- export * as Resolver from "./managed/resolver/contract/index.cjs";
3
- export * as UnifiedDomain from "./managed/unified-domain/contract/index.cjs";
4
- export * as NS from "./managed/ns/contract/index.cjs";
5
- export * from "./witnesses.js";
6
- export { type RegistryRecord } from "./managed/registry/contract/index.cjs";
7
- export { type ResolutionData } from "./managed/resolver/contract/index.cjs";
8
- export { type DomainData as UnifiedDomainData } from "./managed/unified-domain/contract/index.cjs";
9
- export {
10
- type DomainData,
11
- type DomainReference,
12
- } from "./managed/ns/contract/index.cjs";
13
- */
14
1
  export * as Leaf from "./managed/leaf/contract/index.js";
15
2
  export { ledger, Contract } from "./managed/leaf/contract/index.js";
3
+ export { domainToKey, keyToDomain } from "./utils.js";
16
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,IAAI,MAAM,yBAAyB,CAAC;AAChD,OAAO,EAML,MAAM,EACN,QAAQ,EACT,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,yBAAyB,CAAC;AAChD,OAAO,EAML,MAAM,EACN,QAAQ,EACT,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
@@ -1,4 +1,4 @@
1
- pragma language_version 0.20;
1
+ pragma language_version >= 0.20;
2
2
  /// # Unshielded Nameservice
3
3
  ///
4
4
  /// Example:
@@ -26,19 +26,19 @@ import CompactStandardLibrary;
26
26
 
27
27
  // This is sealed. If the domain is sold in the parent contract
28
28
  // The new owner should deploy a new resolver.
29
- export sealed ledger PARENT_DOMAIN: Maybe<Opaque<'string'>>;
29
+ export sealed ledger PARENT_DOMAIN: Maybe<Bytes<32>>;
30
30
  export sealed ledger PARENT_RESOLVER: ContractAddress;
31
- export sealed ledger DOMAIN: Maybe<Opaque<'string'>>;
31
+ export sealed ledger DOMAIN: Maybe<Bytes<32>>;
32
32
 
33
33
  export ledger DOMAIN_OWNER: ZswapCoinPublicKey;
34
34
  // This domain could target either a wallet or a contract
35
35
  export ledger DOMAIN_TARGET: Either<ZswapCoinPublicKey, ContractAddress>;
36
36
 
37
37
  // Map<domain, (owner, resolver)>.
38
- export ledger domains: Map<Opaque<'string'>, DomainData>;
38
+ export ledger domains: Map<Bytes<32>, DomainData>;
39
39
 
40
40
  // Set of domains saved here owned by address.
41
- export ledger domains_owned: Map<ZswapCoinPublicKey, Set<Opaque<'string'>>>;
41
+ export ledger domains_owned: Map<ZswapCoinPublicKey, Set<Bytes<32>>>;
42
42
 
43
43
  export ledger fields: Map<Opaque<'string'>, Opaque<'string'>>;
44
44
 
@@ -51,12 +51,14 @@ struct DomainData {
51
51
 
52
52
 
53
53
  constructor(
54
- parent_domain: Maybe<Opaque<'string'>>,
54
+ parent_domain: Maybe<Bytes<32>>,
55
55
  parent_resolver: ContractAddress,
56
56
  target: Either<ZswapCoinPublicKey, ContractAddress>,
57
- domain: Maybe<Opaque<'string'>>,
57
+ domain: Maybe<Bytes<32>>,
58
58
  coin_color: Bytes<32>,
59
- domain_cost: Uint<128>,
59
+ cost_short: Uint<128>,
60
+ cost_med: Uint<128>,
61
+ cost_long: Uint<128>,
60
62
  kvs: Vector<10, Maybe<[Opaque<'string'>, Opaque<'string'>]>>
61
63
  ) {
62
64
  DOMAIN_OWNER = ownPublicKey();
@@ -65,7 +67,9 @@ constructor(
65
67
  DOMAIN_TARGET = disclose(target);
66
68
  DOMAIN = disclose(domain);
67
69
  COIN_COLOR = disclose(coin_color);
68
- DOMAIN_COST = disclose(domain_cost);
70
+ COST_SHORT = disclose(cost_short);
71
+ COST_MED = disclose(cost_med);
72
+ COST_LONG = disclose(cost_long);
69
73
  for (const kv of kvs) {
70
74
  if (disclose(kv) != none<[Opaque<'string'>, Opaque<'string'>]>()) {
71
75
  fields.insert(disclose(kv.value[0]),disclose(kv.value[1]));
@@ -78,16 +82,20 @@ constructor(
78
82
  // ===========================================
79
83
 
80
84
  export ledger COIN_COLOR: Bytes<32>;
81
- export ledger DOMAIN_COST: Uint<128>;
85
+ export ledger COST_SHORT: Uint<128>; // ≤3 chars
86
+ export ledger COST_MED: Uint<128>; // 4 chars
87
+ export ledger COST_LONG: Uint<128>; // 5+ chars
82
88
 
83
- export circuit update_color(c: Bytes<32>) : [] {
89
+ export circuit update_color(c: Bytes<32>): [] {
84
90
  assert(DOMAIN_OWNER == ownPublicKey(), "Not the owner");
85
91
  COIN_COLOR = disclose(c);
86
92
  }
87
93
 
88
- export circuit update_cost(c: Uint<64>) : [] {
94
+ export circuit update_costs(cost_short: Uint<128>, cost_med: Uint<128>, cost_long: Uint<128>): [] {
89
95
  assert(DOMAIN_OWNER == ownPublicKey(), "Not the owner");
90
- DOMAIN_COST = disclose(c);
96
+ COST_SHORT = disclose(cost_short);
97
+ COST_MED = disclose(cost_med);
98
+ COST_LONG = disclose(cost_long);
91
99
  }
92
100
 
93
101
  export circuit update_target_and_fields(
@@ -104,21 +112,43 @@ export circuit update_target_and_fields(
104
112
 
105
113
  }
106
114
 
107
- export circuit buy_domain_for(owner: ZswapCoinPublicKey, domain: Opaque<'string'>, resolver: ContractAddress): [] {
115
+ export circuit buy_domain_for(owner: ZswapCoinPublicKey, domain: Bytes<32>, len: Uint<32>, resolver: ContractAddress): [] {
116
+ assert(len <= 32, "len must be <= 32");
117
+ assert(len >= 1, "domain name cannot be empty");
118
+
119
+ const valid = fold(
120
+ (acc: Boolean, byte: Uint<8>, i: Uint<8>): Boolean =>
121
+ acc && (((i as Uint<32>) < len) || (byte == 255)),
122
+ true, domain,
123
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
124
+ );
125
+ assert(valid, "Invalid domain key padding");
126
+
108
127
  assert(!domains.member(disclose(domain)), "Domain already exists");
109
128
 
110
- // receiveUnshielded(COIN_COLOR, DOMAIN_COST);
111
- // sendUnshielded(COIN_COLOR, DOMAIN_COST, right<ContractAddress, UserAddress>(UserAddress { bytes: DOMAIN_OWNER.bytes }));
129
+ // pricing: shared color, per-tier cost
130
+ const d_len = disclose(len);
131
+ if (d_len <= 3) {
132
+ receiveUnshielded(COIN_COLOR, COST_SHORT);
133
+ sendUnshielded(COIN_COLOR, COST_SHORT, right<ContractAddress, UserAddress>(UserAddress { bytes: DOMAIN_OWNER.bytes }));
134
+ } else if (d_len == 4) {
135
+ receiveUnshielded(COIN_COLOR, COST_MED);
136
+ sendUnshielded(COIN_COLOR, COST_MED, right<ContractAddress, UserAddress>(UserAddress { bytes: DOMAIN_OWNER.bytes }));
137
+ } else {
138
+ receiveUnshielded(COIN_COLOR, COST_LONG);
139
+ sendUnshielded(COIN_COLOR, COST_LONG, right<ContractAddress, UserAddress>(UserAddress { bytes: DOMAIN_OWNER.bytes }));
140
+ }
112
141
 
113
142
  const d_owner = disclose(owner);
114
143
  const domain_data = DomainData {
115
144
  d_owner,
116
145
  disclose(resolver)
117
146
  };
118
- domains.insert(disclose(domain), disclose(domain_data));
119
- // Track domains owned by address
147
+ domains.insert(disclose(domain), disclose(domain_data));
148
+
149
+ // Track domains owned by address
120
150
  if (!domains_owned.member(d_owner)) {
121
- domains_owned.insert(d_owner, default<Set<Opaque<'string'>>>);
151
+ domains_owned.insert(d_owner, default<Set<Bytes<32>>>);
122
152
  }
123
153
  domains_owned.lookup(d_owner).insert(disclose(domain));
124
154
  }
@@ -142,12 +172,23 @@ export circuit clear_field(k: Opaque<'string'>): [] {
142
172
  fields.remove(disclose(k));
143
173
  }
144
174
 
145
- export circuit clear_all_fields(domain: Opaque<'string'>): [] {
175
+ export circuit clear_all_fields(): [] {
146
176
  assert(DOMAIN_OWNER == ownPublicKey(), "Not the domain owner");
147
177
  fields.resetToDefault();
148
178
  }
149
179
 
150
- export circuit register_domain_for(owner: ZswapCoinPublicKey, domain: Opaque<'string'>, resolver: ContractAddress): [] {
180
+ export circuit register_domain_for(owner: ZswapCoinPublicKey, domain: Bytes<32>, len: Uint<32>, resolver: ContractAddress): [] {
181
+ assert(len <= 32, "len must be <= 32");
182
+ assert(len >= 1, "domain name cannot be empty");
183
+
184
+ const valid = fold(
185
+ (acc: Boolean, byte: Uint<8>, i: Uint<8>): Boolean =>
186
+ acc && (((i as Uint<32>) < len) || (byte == 255)),
187
+ true, domain,
188
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
189
+ );
190
+ assert(valid, "Invalid domain key padding");
191
+
151
192
  assert(!domains.member(disclose(domain)), "Domain already exists");
152
193
  assert(DOMAIN_OWNER == ownPublicKey(), "Not the domain owner");
153
194
 
@@ -161,12 +202,12 @@ export circuit register_domain_for(owner: ZswapCoinPublicKey, domain: Opaque<'st
161
202
 
162
203
  // Track domains owned by address
163
204
  if (!domains_owned.member(d_owner)) {
164
- domains_owned.insert(d_owner, default<Set<Opaque<'string'>>>);
205
+ domains_owned.insert(d_owner, default<Set<Bytes<32>>>);
165
206
  }
166
207
  domains_owned.lookup(d_owner).insert(disclose(domain));
167
208
  }
168
209
 
169
- export circuit set_resolver(domain: Opaque<'string'>, resolver: ContractAddress): [] {
210
+ export circuit set_resolver(domain: Bytes<32>, resolver: ContractAddress): [] {
170
211
  assert(domains.member(disclose(domain)), "Domain does not exist");
171
212
 
172
213
  const current_data = domains.lookup(disclose(domain));
@@ -186,7 +227,7 @@ export circuit update_domain_target(new_target: Either<ZswapCoinPublicKey, Contr
186
227
  DOMAIN_TARGET = disclose(new_target);
187
228
  }
188
229
 
189
- export circuit transfer_domain(domain: Opaque<'string'>, new_owner: ZswapCoinPublicKey): [] {
230
+ export circuit transfer_domain(domain: Bytes<32>, new_owner: ZswapCoinPublicKey): [] {
190
231
  assert(domains.member(disclose(domain)), "Domain does not exist");
191
232
 
192
233
  const current_data = domains.lookup(disclose(domain));
@@ -202,7 +243,7 @@ export circuit transfer_domain(domain: Opaque<'string'>, new_owner: ZswapCoinPub
202
243
  // Update ownership tracking
203
244
  domains_owned.lookup(ownPublicKey()).remove(disclose(domain));
204
245
  if (!domains_owned.member(disclose(new_owner))) {
205
- domains_owned.insert(disclose(new_owner), default<Set<Opaque<'string'>>>);
246
+ domains_owned.insert(disclose(new_owner), default<Set<Bytes<32>>>);
206
247
  }
207
248
  domains_owned.lookup(disclose(new_owner)).insert(disclose(domain));
208
249
  }
@@ -216,12 +257,12 @@ export circuit change_owner(new_owner: ZswapCoinPublicKey) : [] {
216
257
 
217
258
  /*
218
259
  Read only, but not pure, so let's not use those
219
- export circuit resolve(domain: Opaque<'string'>): DomainData {
260
+ export circuit resolve(domain: Bytes<32>): DomainData {
220
261
  assert(domains.member(disclose(domain)), "Domain does not exist");
221
262
  return domains.lookup(disclose(domain));
222
263
  }
223
264
 
224
- export circuit get_parent_domain(): Maybe<Opaque<'string'>> {
265
+ export circuit get_parent_domain(): Maybe<Bytes<32>> {
225
266
  return PARENT_DOMAIN;
226
267
  }
227
268
 
@@ -229,7 +270,7 @@ export circuit get_contract_owner(): ZswapCoinPublicKey {
229
270
  return DOMAIN_OWNER;
230
271
  }
231
272
 
232
- export circuit get_domain(): Maybe<Opaque<'string'>> {
273
+ export circuit get_domain(): Maybe<Bytes<32>> {
233
274
  return DOMAIN;
234
275
  }
235
276
 
@@ -241,7 +282,7 @@ export circuit get_parent_resolver(): ContractAddress {
241
282
  return PARENT_RESOLVER;
242
283
  }
243
284
 
244
- export circuit addr_owns_domain(addr: ZswapCoinPublicKey, domain: Opaque<'string'>): Boolean {
285
+ export circuit addr_owns_domain(addr: ZswapCoinPublicKey, domain: Bytes<32>): Boolean {
245
286
  assert(domains_owned.member(disclose(addr)), "Address does not own domains in this register");
246
287
  return domains_owned.lookup(disclose(addr)).member(disclose(domain));
247
288
  }