ביליתי את השבוע האחרון בניסיון להבין איך דומיינים של .night באמת עובדים. לא מתוך דף מפרט טכני, אלא על ידי בניית משהו שנוגע בשרשרת (chain) ישירות. התוצאה היא צופה פרופיל (profile viewer) קטן. מקלידים שם כמו tomin.night, והוא פותר (resolves) את הפרופיל ישירות מהבלוקצ'יין. בלי API של רשם (registrar). בלי חומת אימות (auth wall). רק חוזה חכם ומעט JavaScript.

מה שמוצג בהמשך הוא איך זה עובד, מה למדתי, ושתי מלכודות ספציפיות שגזלו ממני את כל אחר הצהריים.

למה שמות On-Chain הם חשובים

ב-Midnight, דומיינים של .night מנוהלים על ידי Midnames. במקום לשאול שרת מרכזי של חברה, אתם קוראים ישירות מחוזה חכם שחי על הרשת. ה-Registry עצמו מחזיק בדומיין, בבעלים, ובכל שדה פרופיל שהבעלים צירף. מכיוון שהנתונים הללו הם On-chain, הזהות היא ניידת (portable). אתם לא שוכרים אותה מפלטפורמה שיכולה לשנות תנאים או לנתק את השירות. אם אתם שולטים במפתחות, אתם שולטים בשם.

השינוי הזה חשוב גם למפתחים. כשבונים מול מערכת DNS מסורתית, מתמודדים עם מגבלות קצב (rate limits), מפתחות API והבטחות uptime. כאן, מצב החוזה (contract state) הוא מקור האמת (source of truth). האפליקציה שלכם קוראת אותו באותו אופן שבו כל אפליקציה אחרת קוראת אותו. אין שכבת API מועדפת.

הקוד כמעט פשוט מדי

ה-@midnames/sdk עושה את העבודה הקשה. פתרון (resolving) של דומיין לכתובת ופרופיל לוקח בדיוק שתי שורות:

const provider = createDefaultProvider({ networkId: "mainnet" });
const result = await resolveDomain(provider, "tomin.night");

זהו זה. ה-provider מכוון לרשת, וה-resolver מדבר עם החוזה. ה-SDK מחזיר אובייקט תוצאה הכולל דגל הצלחה (success flag). אם הדומיין לא קיים, אין צורך בשכבות של טיפול בשגיאות או בבלוקים של try-catch סביב כשלי RPC. ה-SDK אומר לכם בצורה נקייה ששום דבר לא נמצא שם. זה הופך את בניית ממשקי המשתמש (UIs) לנעימה באופן מפתיע. אתם יכולים לבצע פיצול (branch) על בסיס דגל ההצלחה ולהציג מצב של "לא נמצא" מבלי לנחש אם הכישלון נבע משם חסר או מנודה (node) שאינה פעילה.

מה שמקבלים בחזרה

כאשר החיפוש מצליח, ה-payload מכיל שני חלקים חשובים.

Target הוא כתובת הארנק שאליה הדומיין מפנה. זהו השימוש המרכזי. זה הופך כתובת hex ארוכה למשהו שבן אדם יכול לקרוא, להקליד ולזכור.

Fields מחזיקים את פרטי הפרופיל. כל מה שהבעלים צירף לדומיין — קישורים חברתיים, אווטארים, רשומות טקסט — חי בתוך המבנה הזה. השדות הללו אינם מאוחסנים ב-MongoDB cluster של איזושהי חברה. הם שדות במצב החוזה (contract state), מה שאומר שכל אפליקציה שיודעת לקרוא את ה-registry יכולה להציג את אותו פרופיל. אין צורך בסנכרון מסדי נתונים.

שתי מלכודות שעיכבו אותי

לא כל מה שבנייה היה שתי שורות ודגל הצלחה. נתקלתי בשני מכשולים ספציפיים שכדאי לפרט כדי שלא תחזרו עליהם.

חוסר התאמה ברשת (Network Mismatch)

ה-SDK תומך גם בסביבות mainnet וגם ב-preprod. ביליתי זמן רב בפתרון באגים של דומיין ש"לא היה קיים". השם היה נכון. הקוד נראה תקין. ה-provider היה פעיל. הבעיה הייתה שהסקריפט שלי ביצע שאילתה ב-preprod בעוד שהדומיין עצמו היה רשום ב-mainnet. השגיאה נראתה כמו דומיין חסר, אבל היא הייתה באמת חוסר בהקשר הרשת (network context).

אם אתם פותרים שם ומקבלים כשל, בדקו את הגדרות ה-provider לפני שאתם מנסים לדבג דברים אחרים. ודאו שה-networkId שלכם תואם לרשת שבה הדומיין אכן מונח (minted). זה סוג הטעות שנראית מובנת מאליה בדיעבד, אבל באמת קשה להבחין בה כשאתם מניחים שהבעיה היא בלוגיקה של החוזה.

כאב של סריאליזציה (Serialization Pain)

הנתונים שחוזרים מה-SDK אינם JavaScript סטנדרטי. הם מכילים ערכי BigInt ואובייקטים מסוג Map. אם תנסו להעביר אותם ישירות ל-JSON.stringify כדי לשלוח אותם לדפדפן, זה יזרוק שגיאה או יאבד נתונים בשקט. ל-BigInt אין ייצוג JSON מובנה, ו-Map לא עובר סריאליזציה באותו אופן שבו אובייקטים רגילים עוברים.

סיום בסוף כתבתי סריאליזטור מותאם אישית. הוא עובר על אובייקט התוצאה, ממיר ערכי BigInt למחרוזות (strings), ומתמיר מופעי Map לאובייקטים רגילים לפני שהתגובה עוזבת את השרת. אם אתם בונים API שמגיש נתוני Midnight לצד לקוח (frontend), תכננו את השלב הזה מראש. אל תניחו שפלט ה-SDK ידידותי מיד ל-frontend רק בגלל שהוא JavaScript.

הארכיטקטורה

שמרתי על ה-stack משעמם בכוונה. ה-backend הוא שרת Node שמריץ Express. הוא מייבא את ה-@midnames/sdk, מריץ את לוגיקת ה-resolution, מטפל ב"אקרובטיקה" של ה-serialization, ומגיש JSON נקי. ה-frontend הוא HTML פשוט ו-vanilla JavaScript. בלי שלב build. בלי framework. בלי wallet adapter.

בחרתי להריץ את ה-SDK ב-backend במקום בדפדפן מכמה סיבות מעשיות. זה שומר על כל הגדרת provider מחוץ ללקוח (client), זה נותן לי מקום אחד לתיקון הבלאגן של ה-serialization, וזה אומר שה-frontend צריך רק למשוך נתונים ולרנדר אותם.

הנה החלק שהפתיע אותי ביותר: פתרון שם (resolving a name) הוא פעולת קריאה ציבורית. אין צורך בחיבור ארנק (wallet connection). אין צורך בחתימה. אין צורך שהמשתמש יתחבר עם שום דבר. אם הדומיין קיים, מצב החוזה (contract state) גלוי לכל מי ששואל. זהו הבדל משמעותי מהזרימה הטיפוסית של web3, שבה כל אינטראקציה מתחילה ב-"connect wallet". קריאת זהות ב-Midnight היא ללא צורך באישור (permissionless), בדיוק כפי שקריאת אתר אינטרנט ציבורי היא ללא צורך באישור.

הלקח האמיתי

בניית ה-viewer הזה הזכירה לי שהחלק הקשה ביותר בפיתוח בלוקצ'יין הוא לעיתים רחוקות הבלוקצ'יין עצמו. Midnight כבר פתרה את הבעיה הקשה: לאפשר לאנשים להחזיק בשם ובפרופיל שלהם ללא מסד נתונים מרכזי. החלק הקשה, מנקודת מבטו של בונה (builder), היה לזכור לאיזו רשת הצבעתי ולכתוב פונקציית עזר (helper function) כדי לנקות את סוגי הנתונים (data types).

הפרוטוקול מעניק לך זהות ניידת (portable identity). התפקיד שלך כמפתח הוא פשוט לקרוא אותה נכון ולהתרחק מדרכו של המשתמש. שמור על ארכיטקטורה פשוטה, הפרד את הלוגיקה הפונה לשרשרת (chain-facing logic) מה-UI, והתייחס לקריאות ציבוריות כפי שהן: שאילתות מסד נתונים רגילות שפשוט נמצאות על ספר חשבונות מבוזר (distributed ledger).

אם ברצונך לראות את הקוד או להריץ אותו בעצמך, הקוד המקור המלא זמין בכתובת https://github.com/tomiin/midnames-profile-viewer.