গত সপ্তাহটি আমি বোঝার চেষ্টা করেছি যে .night ডোমেইনগুলো আসলে কীভাবে কাজ করে। কোনো স্পেক শিট থেকে নয়, বরং সরাসরি চেইনের সাথে যুক্ত এমন কিছু তৈরি করার মাধ্যমে। এর ফলাফল হলো একটি ছোট প্রোফাইল ভিউয়ার। আপনি tomin.night-এর মতো একটি নাম টাইপ করবেন, এবং এটি সরাসরি ব্লকচেইন থেকে প্রোফাইলটি রিজলভ করবে। কোনো রেজিস্ট্রার API নেই। কোনো অথেন্টিকেশন ওয়াল নেই। শুধু একটি স্মার্ট কন্ট্রাক্ট এবং কিছু JavaScript।

নিচে এর কার্যপদ্ধতি, আমি যা শিখেছি এবং সেই দুটি নির্দিষ্ট ফাঁদ সম্পর্কে বলা হয়েছে যা আমার অনেকটা সময় নষ্ট করে দিয়েছিল।

কেন অন-চেইন নামগুলো গুরুত্বপূর্ণ

Midnight-এ, .night ডোমেইনগুলো Midnames দ্বারা পরিচালিত হয়। কোনো কোম্পানির সেন্ট্রাল সার্ভার কুয়েরি করার পরিবর্তে, আপনি সরাসরি চেইনে থাকা একটি স্মার্ট কন্ট্রাক্ট থেকে তথ্য পড়তে পারেন। রেজিস্ট্রি নিজেই ডোমেইন, মালিক এবং মালিক যে প্রোফাইল ফিল্ডগুলো যুক্ত করেছেন তা ধারণ করে। যেহেতু এই ডেটা অন-চেইন থাকে, তাই এই আইডেন্টিটি পোর্টেবল বা স্থানান্তরযোগ্য। আপনি এটি এমন কোনো প্ল্যাটফর্ম থেকে ভাড়া নিচ্ছেন না যা শর্তাবলী পরিবর্তন করতে পারে বা পরিষেবা বন্ধ করে দিতে পারে। আপনি যদি কী (keys) নিয়ন্ত্রণ করেন, তবে আপনি নামটি নিয়ন্ত্রণ করেন।

এই পরিবর্তনটি ডেভেলপারদের জন্যও গুরুত্বপূর্ণ। যখন আপনি একটি প্রথাগত DNS সিস্টেমের বিপরীতে কাজ করেন, তখন আপনাকে রেট লিমিট, API কী এবং আপটাইম প্রমিজ নিয়ে ভাবতে হয়। এখানে, কন্ট্রাক্ট স্টেট হলো সত্যের উৎস (source of truth)। আপনার অ্যাপ্লিকেশনটি এটি ঠিক একইভাবে পড়ে যেভাবে অন্য সব অ্যাপ্লিকেশন পড়ে। এখানে কোনো বিশেষাধিকারপ্রাপ্ত (privileged) API টায়ার নেই।

কোডটি প্রায় অবিশ্বাস্য রকমের সহজ

@midnames/sdk সব কঠিন কাজগুলো সামলে নেয়। একটি ডোমেইনকে অ্যাড্রেস এবং প্রোফাইলে রিজলভ করতে ঠিক মাত্র দুটি লাইন লাগে:

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

ব্যস, এটুকুই। প্রোভাইডার নেটওয়ার্ককে টার্গেট করে এবং রিজলভার কন্ট্রাক্টের সাথে কথা বলে। SDK একটি রেজাল্ট অবজেক্ট রিটার্ন করে যাতে একটি success flag থাকে। যদি ডোমেইনটি না থাকে, তবে আপনার RPC ফেইলিউরের জন্য অনেক স্তরের এরর হ্যান্ডলিং বা try-catch ব্লকের প্রয়োজন হয় না। SDK আপনাকে পরিষ্কারভাবে জানিয়ে দেয় যে সেখানে কিছু নেই। এটি UI তৈরি করাকে আশ্চর্যজনকভাবে সহজ করে তোলে। আপনি success flag-এর ওপর ভিত্তি করে সিদ্ধান্ত নিতে পারেন এবং ব্যর্থতাটি একটি অনুপস্থিত নাম নাকি একটি মৃত নোড ছিল তা অনুমান না করেই একটি “not found” স্টেট দেখাতে পারেন।

আপনি যা ফেরত পাবেন

যখন লুকআপ সফল হয়, তখন পেলোডটিতে দুটি গুরুত্বপূর্ণ অংশ থাকে।

Target হলো সেই ওয়ালেট অ্যাড্রেস যার দিকে ডোমেইনটি নির্দেশ করে। এটিই এর মূল উপযোগিতা। এটি একটি দীর্ঘ hex অ্যাড্রেসকে এমন কিছুতে রূপান্তরিত করে যা একজন মানুষ পড়তে, টাইপ করতে এবং মনে রাখতে পারে।

Fields প্রোফাইলের বিস্তারিত তথ্য ধারণ করে। মালিক ডোমেইনের সাথে যা কিছু যুক্ত করেছেন—সোশ্যাল লিঙ্ক, অবতার, টেক্সট রেকর্ড—সবই এই স্ট্রাকচারের ভেতরে থাকে। এই ফিল্ডগুলো কোনো কোম্পানির MongoDB ক্লাস্টারে সংরক্ষিত থাকে না। এগুলো কন্ট্রাক্ট স্টেটের ফিল্ড, যার মানে হলো যে কোনো অ্যাপ যা রেজিস্ট্রি পড়তে জানে, সেটি একই প্রোফাইল রেন্ডার করতে পারে। কোনো ডাটাবেস সিঙ্ক করার প্রয়োজন নেই।

দুটি ফাঁদ যা আমার গতি কমিয়ে দিয়েছিল

তৈরির পুরো প্রক্রিয়াটি কেবল দুটি লাইন এবং একটি success flag-এর মতো সহজ ছিল না। আমি দুটি নির্দিষ্ট বাধার সম্মুখীন হয়েছি যা স্পষ্টভাবে বলা উচিত যাতে আপনি সেগুলো পুনরাবৃত্তি না করেন।

Network Mismatch

SDK উভয় mainnet এবং preprod এনভায়রনমেন্ট সমর্থন করে। আমি একটি ডোমেইন যা “অস্তিত্ব নেই” তা ডিবাগ করতে অনেকটা সময় ব্যয় করেছি। নামটি সঠিক ছিল। কোডটিও ঠিক ছিল। প্রোভাইডারটিও চলছিল। সমস্যাটি ছিল যে আমার স্ক্রিপ্টটি preprod কুয়েরি করছিল যেখানে ডোমেইনটি আসলে mainnet-এ নিবন্ধিত ছিল। এররটি দেখে মনে হচ্ছিল ডোমেইনটি নেই, কিন্তু আসলে এটি ছিল নেটওয়ার্ক কনটেক্সটের অভাব।

আপনি যদি একটি নাম রিজলভ করতে গিয়ে ব্যর্থ হন, তবে অন্য কিছু ডিবাগ করার আগে প্রোভাইডার সেটিংস পরীক্ষা করে দেখুন। নিশ্চিত করুন যে আপনার networkId সেই নেটওয়ার্কের সাথে মিলে যাচ্ছে যেখানে ডোমেইনটি আসলে মিন্ট (mint) করা হয়েছে। এটি এমন এক ধরণের ভুল যা পরে দেখলে খুব সহজ মনে হয়, কিন্তু যখন আপনি ধরে নেন যে কন্ট্রাক্ট লজিকই সমস্যা, তখন এটি খুঁজে পাওয়া সত্যিই কঠিন।

Serialization Pain

SDK থেকে আসা ডেটা সাধারণ JavaScript নয়। এতে BigInt ভ্যালু এবং Map অবজেক্ট থাকে। আপনি যদি এটি সরাসরি ব্রাউজারে পাঠানোর জন্য JSON.stringify-তে ব্যবহার করার চেষ্টা করেন, তবে এটি এরর দেবে অথবা নীরবে ডেটা হারিয়ে ফেলবে। BigInt-এর কোনো নেটিভ JSON রিপ্রেজেন্টেশন নেই এবং Map সাধারণ অবজেক্টের মতো সিরিয়ালাইজ হয় না।

শেষ পর্যন্ত আমাকে একটি কাস্টম সিরিয়ালাইজার লিখতে হয়েছে। এটি রেজাল্ট অবজেক্টটি স্ক্যান করে, BigInt ভ্যালুগুলোকে স্ট্রিং-এ রূপান্তর করে এবং রেসপন্সটি সার্ভার থেকে বের হওয়ার আগে Map ইনস্ট্যান্সগুলোকে সাধারণ অবজেক্টে রূপান্তরিত করে। আপনি যদি এমন কোনো API তৈরি করেন যা ফ্রন্টএন্ডে Midnight ডেটা সরবরাহ করে, তবে শুরুতেই এই ধাপটির জন্য পরিকল্পনা করুন। শুধুমাত্র এটি JavaScript হওয়ার কারণে SDK আউটপুট সরাসরি ফ্রন্টএন্ড-ফ্রেন্ডলি হবে বলে ধরে নেবেন না।

আর্কিটেকচার

আমি স্ট্যাকটিকে উদ্দেশ্যমূলকভাবে খুব সাধারণ রেখেছি। ব্যাকএন্ড হলো একটি Node সার্ভার যা Express ব্যবহার করে চলে। এটি @midnames/sdk ইম্পোর্ট করে, রেজোলিউশন লজিক চালায়, সিরিয়ালাইজেশনের জটিলতাগুলো সামলায় এবং ক্লিন JSON প্রদান করে। ফ্রন্টএন্ড হলো সাধারণ HTML এবং ভ্যানিলা JavaScript। কোনো বিল্ড স্টেপ নেই। কোনো ফ্রেমওয়ার্ক নেই। কোনো ওয়ালেট অ্যাডাপ্টার নেই।

আমি কিছু ব্যবহারিক কারণে ব্রাউজারের পরিবর্তে ব্যাকএন্ডে SDK চালানোর সিদ্ধান্ত নিয়েছি। এটি ক্লায়েন্টের বাইরে প্রোভাইডার কনফিগারেশনগুলো সুরক্ষিত রাখে, সিরিয়ালাইজেশনের বিশৃঙ্খলা ঠিক করার জন্য আমাকে একটি নির্দিষ্ট জায়গা দেয়, এবং এর মানে হলো ফ্রন্টএন্ডের কাজ শুধু ডেটা ফেচ করা এবং তা রেন্ডার করা।

সবচেয়ে অবাক করা বিষয়টি হলো: একটি নাম রেজলভ করা একটি পাবলিক রিড অপারেশন। এর জন্য আপনার কোনো ওয়ালেট কানেকশন প্রয়োজন নেই। কোনো সিগনেচারের প্রয়োজন নেই। ব্যবহারকারীকে কোনো কিছুর মাধ্যমে লগ ইন করতেও হয় না। যদি ডোমেইনটি বিদ্যমান থাকে, তবে কন্ট্রাক্ট স্টেট যে কেউ দেখতে পারে। এটি সাধারণ web3 ফ্লো থেকে একটি উল্লেখযোগ্য পার্থক্য, যেখানে প্রতিটি ইন্টারঅ্যাকশন “connect wallet” দিয়ে শুরু হয়। Midnight-এ আইডেন্টিটি পড়া ঠিক তেমনই পারমিশনলেস, যেমনটি একটি পাবলিক ওয়েবসাইট পড়া।

আসল শিক্ষা

এই ভিউয়ারটি তৈরি করতে গিয়ে আমার মনে হলো যে, ব্লকচেইন ডেভেলপমেন্টের সবচেয়ে কঠিন অংশটি খুব কমই স্বয়ং ব্লকচেইন নিজে। Midnight ইতিমধ্যে কঠিন সমস্যাটি সমাধান করে ফেলেছে: কোনো সেন্ট্রাল ডেটাবেস ছাড়াই মানুষকে তাদের নাম এবং প্রোফাইলের মালিকানা প্রদান করা। একজন বিল্ডারের দৃষ্টিকোণ থেকে কঠিন অংশটি ছিল আমি কোন নেটওয়ার্কের দিকে নির্দেশ করছি তা মনে রাখা এবং ডেটা টাইপগুলো পরিষ্কার করার জন্য একটি হেল্পার ফাংশন লেখা।

প্রোটোকল আপনাকে পোর্টেবল আইডেন্টিটি প্রদান করে। একজন ডেভেলপার হিসেবে আপনার কাজ হলো কেবল এটি সঠিকভাবে পড়া এবং ব্যবহারকারীর কাজে বাধা না দেওয়া। আর্কিটেকচার সহজ রাখুন, চেইন-ফেসিং লজিককে UI থেকে আলাদা রাখুন এবং পাবলিক রিডগুলোকে ঠিক যা তারা, তেমনটিই বিবেচনা করুন: সাধারণ ডেটাবেস কুয়েরি যা কেবল একটি ডিস্ট্রিবিউটেড লেজারে অবস্থান করছে।

আপনি যদি কোডটি দেখতে চান বা নিজে চালাতে চান, তবে সম্পূর্ণ সোর্স এখানে পাওয়া যাবে: https://github.com/tomiin/midnames-profile-viewer.