<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[SUI Thai Developer Community]]></title><description><![CDATA[Sui Thai Developer Community – resources, tutorials, and discussions about Sui blockchain and Move language for Thai developers.]]></description><link>https://onthemove.contributiondao.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1756874761794/fea06652-47d3-416a-89fd-55f59794500f.png</url><title>SUI Thai Developer Community</title><link>https://onthemove.contributiondao.com</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 24 Apr 2026 23:52:49 GMT</lastBuildDate><atom:link href="https://onthemove.contributiondao.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Sui Stack Messaging SDK: EP 3 Interacting with AI BOT]]></title><description><![CDATA[มาถึง EP สุดท้ายแล้วของ series Sui Stack Messaging SDK ละครับ ซึ่ง EP นี้ไม่ยากละ เพราะจะเอาองค์รวมความรู้ทั้งหมดของ EP ก่อนหน้านี้มาเขียนอีกครั้งนึง เพื่อให้ AI เราตอบโต้กับเราได้ ซึ่งอย่างที่บอกไป A]]></description><link>https://onthemove.contributiondao.com/sui-stack-messaging-sdk-ep-3-interacting-with-ai-bot</link><guid isPermaLink="true">https://onthemove.contributiondao.com/sui-stack-messaging-sdk-ep-3-interacting-with-ai-bot</guid><category><![CDATA[Sui]]></category><category><![CDATA[onthemoveth]]></category><category><![CDATA[sui-messaging-sdk]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Fri, 17 Apr 2026 07:11:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68b7c67e9e994bcb96821cae/f5150700-fbc7-45cd-acb9-f44f2859a473.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>มาถึง EP สุดท้ายแล้วของ series Sui Stack Messaging SDK ละครับ ซึ่ง EP นี้ไม่ยากละ เพราะจะเอาองค์รวมความรู้ทั้งหมดของ EP ก่อนหน้านี้มาเขียนอีกครั้งนึง เพื่อให้ AI เราตอบโต้กับเราได้ ซึ่งอย่างที่บอกไป AI ให้มองแยกเป็น interface นึง ส่วนวิธีการติดต่อก็จะเหมือนเดิมเลยครับ ใครยังไม่ตามลองไปทำ 2 EP ก่อนหน้าก่อนนะ</p>
<ul>
<li><p><a href="https://onthemove.contributiondao.com/sui-stack-messaging-sdk-introduction">https://onthemove.contributiondao.com/sui-stack-messaging-sdk-introduction</a> (EP 1)</p>
</li>
<li><p><a href="https://onthemove.contributiondao.com/sui-stack-messaging-sdk-ep-2-first-step-encrypted-chat-cli">https://onthemove.contributiondao.com/sui-stack-messaging-sdk-ep-2-first-step-encrypted-chat-cli</a> (EP 2)</p>
</li>
</ul>
<p>สิ่งที่เราจะต้องเตรียมคือ</p>
<ul>
<li><p>Wallet สำหรับ AI</p>
</li>
<li><p>AI API KEY ขึ้นกับถนัดนะ ใครอยากใช้ตัวไหนก็ตามสบายเลย</p>
</li>
</ul>
<p>ซึ่งก่อนหน้านี้เราสร้าง CLI chatroom ที่ส่งข้อความ encrypted ได้แล้วใช่ไหม วันนี้จะมาเพิ่มสิ่งที่ทำให้มันเจ๋งขึ้นอีกขั้น โดยจะทำให้ AI Bot ที่คอยตอบแชทอัตโนมัติ ทุกข้อความที่ bot ส่งก็ encrypted on-chain เหมือนกัน.. หลักการมันไม่มีอะไร แค่มี AI เป็น interface แค่นั้นเอง มาเริ่มกันเลย</p>
<h2>1. AI Wrapper</h2>
<p>เราเลือกใช้ Groq API นะ ไปสร้าง API Key กันได้เลย ฟรี โดยเราจะเริ่มจากสร้าง ตัวกลางระหว่าง bot กับ Groq API ก่อน</p>
<pre><code class="language-typescript">import Groq from "groq-sdk";
import { config } from "../config.js";

const groq = new Groq({ apiKey: config.groqApiKey });

const SYSTEM_PROMPT = `You are a helpful AI assistant in a Web3 chatroom
built on Sui blockchain. Knowledge: Sui, Move, DeFi, Walrus, Seal.

CRITICAL: Messages on-chain cap at 512 bytes.
Thai = 3 bytes/char → keep Thai replies under ~150 chars.
English → under ~400 chars. Be concise, 1-3 sentences max.
Respond in the same language the user writes in.`;

export async function getAIResponse(
  userMessage: string,
  conversationHistory: { role: "user" | "assistant"; content: string }[] = []
): Promise&lt;string&gt; {
  try {
    const messages = [
      { role: "system" as const, content: SYSTEM_PROMPT },
      ...conversationHistory.slice(-10), 
      { role: "user" as const, content: userMessage },
    ];

    const response = await groq.chat.completions.create({
      model: config.groqModel,
      max_tokens: 150,
      messages,
    });

    return response.choices[0]?.message?.content || "🤖 (no response)";
  } catch (err: any) {
    console.error("Groq API error:", err.message);
    return "🤖 Sorry, I'm having trouble connecting right now.";
  }
}
</code></pre>
<p>มีจุดที่ต้องเข้าใจหลักๆ คือ</p>
<ul>
<li><p><strong>SYSTEM_PROMPT</strong> บอก AI ว่ามันคือใคร ตอบเรื่องอะไรได้บ้าง และที่สำคัญคือ<strong>ต้องตอบสั้นๆ</strong> เพราะ channel contract จำกัด message ไว้ที่ 512 bytes ภาษาไทย 1 ตัวอักษร = 3 bytes (UTF-8) ดังนั้นถ้าตอบยาวเกินจะส่งขึ้น chain ไม่ได้เลย error ตลอด อันนี้เเหละที่นั่ง debug อยู่นาน :'(</p>
</li>
<li><p><strong>conversationHistory</strong> เราส่ง 10 ข้อความล่าสุดเข้าไปด้วย ทำให้ bot จำบริบทการสนทนาได้ ไม่ใช่ตอบแบบลืมหมดทุกครั้ง</p>
</li>
<li><p><strong>max_tokens: 150</strong> จำกัดความยาว reply ของ AI ไว้อีกชั้นหนึ่งจะได้ไม่หลุด</p>
</li>
</ul>
<p>ส่วนใครถนัดตัวไหนก็ลองกับตัวนั้นได้เลย ส่วนนี้ไม่มีอะไรเเค่เขียนติดต่อกับ AI</p>
<h2>2. Bot Main Loop</h2>
<p>ส่วนหลักของ EP คือBot ทำงานเป็น <strong>long-running process</strong> มาดู flow ทั้งหมดตั้งแต่เริ่มต้นจนตอบข้อความ</p>
<p>สร้างไฟล์ <code>bot.ts</code></p>
<pre><code class="language-typescript">import chalk from "chalk";
import { createBotClient } from "../client.js";
import { config } from "../config.js";
import { getAIResponse } from "./ai.js";
import { getEncryptionKey } from "../encryption.js";

const conversationHistory: Record&lt;string, { role: "user" | "assistant"; content: string }[]&gt; = {};
const pollingStates: Record&lt;string, {
  lastMessageCount: bigint;
  lastCursor: bigint | null;
  channelId: string;
}&gt; = {};
const encryptionKeyCache: Record&lt;string, Awaited&lt;ReturnType&lt;typeof getEncryptionKey&gt;&gt;&gt; = {};
</code></pre>
<p>เริ่มจาก state ที่เก็บใน memory ทั้งหมด ซึ่งจะมี <code>conversationHistory</code> สำหรับให้ AI จำบริบทได้, <code>pollingStates</code> สำหรับจำว่า poll ถึงข้อความไหนแล้ว, <code>encryptionKeyCache</code> สำหรับ cache key ไม่ต้อง fetch ทุกรอบ</p>
<h3>ฟังก์ชัน pollAndReply ใช้เพื่อ รับข้อความ → ถาม AI → ตอบกลับ</h3>
<p>นี่คือหัวใจจริงๆ ของ bot ทำงาน 3 ขั้นตอนคือ ดึงข้อความใหม่ → ส่งให้ Groq ตอบ → ส่ง reply กลับเข้า channel</p>
<pre><code class="language-typescript">async function pollAndReply(channelId: string, memberCapId: string) {
  const { client, messagingClient, keypair } = createBotClient();
  const botAddress = keypair.toSuiAddress();

  if (!encryptionKeyCache[channelId]) {
    encryptionKeyCache[channelId] = await getEncryptionKey(messagingClient, channelId, botAddress);
  }
  const encryptedKey = encryptionKeyCache[channelId];

  if (!pollingStates[channelId]) {
    const channels = await messagingClient.getChannelObjectsByChannelIds({
      channelIds: [channelId],
      userAddress: botAddress,
    });
    pollingStates[channelId] = {
      lastMessageCount: BigInt(channels[0].messages_count),
      lastCursor: null,
      channelId,
    };
    console.log(chalk.gray(`  Initialized polling for ${channelId.slice(0, 12)}...`));
    return;
  }

  const pollingState = pollingStates[channelId];
  const newMsgs = await messagingClient.getLatestMessages({
    channelId,
    userAddress: botAddress,
    pollingState,
  });

  if (newMsgs.messages.length === 0) return;

  for (const msg of newMsgs.messages) {
    if (msg.sender === botAddress) continue;

    console.log(chalk.cyan(`  📨 \({msg.sender.slice(0, 8)}...: \){msg.text}`));

    if (!conversationHistory[channelId]) conversationHistory[channelId] = [];

    const rawReply = await getAIResponse(msg.text, conversationHistory[channelId]);
    const aiReply = truncateToBytes(rawReply, 480);
    try {
      const { digest } = await messagingClient.executeSendMessageTransaction({
        signer: keypair,
        channelId,
        memberCapId,
        message: aiReply,
        encryptedKey,
      });
      await client.waitForTransaction({ digest });

      console.log(chalk.green(`  🤖 Bot: ${aiReply.slice(0, 80)}...`));
      console.log(chalk.gray(`     🔗 ${config.suiscanTxUrl(digest)}`));

      conversationHistory[channelId].push(
        { role: "user", content: msg.text },
        { role: "assistant", content: aiReply },
      );

      if (conversationHistory[channelId].length &gt; 20) {
        conversationHistory[channelId] = conversationHistory[channelId].slice(-20);
      }
    } catch (err: any) {
      console.log(chalk.red(`  ❌ Failed to reply: ${err.message}`));
      delete encryptionKeyCache[channelId];
    }
  }
  pollingState.lastMessageCount += BigInt(newMsgs.messages.length);
}
</code></pre>
<p>เล่า flow ตามตัวอย่างด้านบนอีกรอบนึงคือ</p>
<ol>
<li><p><strong>ครั้งแรก</strong> bot จะดูว่า channel มีข้อความกี่ข้อความแล้ว แล้วจำตัวเลขนี้ไว้ ข้อความก่อนหน้านี้จะถูกข้ามทั้งหมด</p>
</li>
<li><p><strong>ครั้งถัดไป</strong> bot ถาม SDK ว่ามีข้อความใหม่กว่าตัวเลขที่จำไว้ไหม ถ้ามีก็ดึงมา</p>
</li>
<li><p><strong>กรองข้อความ</strong> ข้ามข้อความที่ bot ส่งเอง ไม่งั่นได้ติดในลูปแน่</p>
</li>
<li><p><strong>ส่งให้ AI</strong> เรียก <code>getAIResponse()</code> พร้อม conversation history</p>
</li>
<li><p><strong>ตัดความยาว</strong> <code>truncateToBytes()</code> ตัดไม่ให้เกิน 480 bytes ซึ่งจะเป็นส่วนช่วยป้องกัน error นั้นเอง</p>
</li>
<li><p><strong>ส่งกลับ</strong> <code>executeSendMessageTransaction()</code> encrypt + store on-chain</p>
</li>
<li><p><strong>รอ finality</strong> <code>waitForTransaction()</code> รอให้ transaction เสร็จก่อน poll รอบถัดไป</p>
</li>
<li><p><strong>จำบริบท</strong> เก็บ history ไว้ให้ AI ตอบครั้งหน้าได้ต่อเนื่อง</p>
</li>
</ol>
<h3>ฟังก์ชัน truncateToBytes — ตัดข้อความให้พอดี 512 bytes</h3>
<p>เราต้องมีฟังก์ชันนี้เป็น safety net อีกทึนึงเพราะแม้จะบอก AI ให้ตอบสั้นแล้ว บางทีมันก็ยังตอบยาวเกินอยู่ดี ใครมีเทคนิดบีบคอให้ AI ไม่งอแงบอกหน่อยนะ ชอบตอบเกินความจำเป็น</p>
<pre><code class="language-typescript">const MAX_MESSAGE_BYTES = 480;

function truncateToBytes(text: string, maxBytes: number): string {
  const bytes = new TextEncoder().encode(text);
  if (bytes.length &lt;= maxBytes) return text;
  let cut = maxBytes - 3;
  while (cut &gt; 0 &amp;&amp; (bytes[cut] &amp; 0xc0) === 0x80) cut--;
  return new TextDecoder().decode(bytes.slice(0, cut)) + "...";
}
</code></pre>
<p>ส่วนนี้เรากำหนดไว้ 480 เพราะถ้า 512 พอดีมันจะมีพวก overhead ข้อมูลอื่นๆด้วยทำให้เกินตลอด</p>
<h3>ฟังก์ชัน main เพื่อเริ่มต้นการทำงาน</h3>
<pre><code class="language-typescript">async function main() {
  const { messagingClient, keypair } = createBotClient();
  const botAddress = keypair.toSuiAddress();

  console.log(chalk.blue("🤖 Sui Chat AI Bot Starting..."));
  console.log(chalk.blue("   Bot wallet:"), botAddress);

  const result = await messagingClient.getChannelMemberships({ address: botAddress });
  const memberships = new Map&lt;string, string&gt;(
    result.memberships.map((m) =&gt; [m.channel_id, m.member_cap_id]),
  );

  if (memberships.size === 0) {
    console.log(chalk.yellow("  Bot is not a member of any channels."));
    console.log(chalk.white(`  Invite first: npm run dev -- invite &lt;channelId&gt; ${botAddress}`));
    process.exit(0);
  }

  console.log(chalk.green(`  Monitoring ${memberships.size} channel(s)`));

  let polling = false;
  const poll = async () =&gt; {
    if (polling) return;
    polling = true;
    try {
      for (const [channelId, memberCapId] of memberships) {
        await pollAndReply(channelId, memberCapId);
      }
    } finally {
      polling = false;
    }
  };

  await poll();
  setInterval(poll, config.botPollIntervalMs);
  console.log(chalk.green("  ✅ Bot is running. Press Ctrl+C to stop.\n"));
}

main().catch((err) =&gt; {
  console.error(chalk.red("Bot crashed:"), err);
  process.exit(1);
});
</code></pre>
<p>ตรง <code>polling</code> flag สำคัญ — ถ้ารอบก่อนยังทำไม่เสร็จ (เช่น Groq ตอบช้า หรือ Sui ยัง finalize ไม่เสร็จ) รอบถัดไปจะถูกข้ามไปเลย ป้องกัน object-lock error</p>
<h2>3. Demo</h2>
<p>วิธีการทดสอบนั้นไม่ยากถ้าใครลองเล่น EP.2 ก็ให้เราสร้างห้องขึ้นมาก่อนแล้วทำการ invite bot เข้าห้องที่เราสร้างนั้นเอง</p>
<pre><code class="language-bash"># Create room
npm run dev -- create-room

# Invite bot to join 
npm run dev -- invite &lt;channelId&gt; 0xBOT_ADDRESS

# Room own join the chat room
npm run dev -- chat &lt;channelId&gt;
</code></pre>
<p>หลังจากนั้นเปิดอีก terminal นึงขึ้นมาเพื่อรันบอท</p>
<pre><code class="language-bash">npm run bot
</code></pre>
<p>เรียบร้อยจ้าเสร็จละ ซึ่งแต่ละข้อความอาจจะต้องรอประมาณ 5-10 วินาที bot จะตอบกลับมาอัตโนมัติ ทุกข้อความ encrypted on-chain ทั้งหมด ลองไปดู transaction บน <a href="https://suiscan.xyz/">https://suiscan.xyz/</a> ได้เลยนะ</p>
<img src="https://cdn.hashnode.com/uploads/covers/68b7c67e9e994bcb96821cae/8347a0fb-543b-4a02-bef7-aae2e5422506.gif" alt="" style="display:block;margin:0 auto" />

<p>ใครขี้เกียจก็ไปตามได้ที่นี้เลย เอา source code ทั้งหมดไว้ละ <a href="https://github.com/Contribution-DAO/ON-THE-MOVE-Workshop/tree/main/sui-messaging-sdk">https://github.com/Contribution-DAO/ON-THE-MOVE-Workshop/tree/main/sui-messaging-sdk</a></p>
<p>แล้วเจอกันในบทความหน้านะ แอบกระซิบว่าเร็วๆนี้ในไทยจะมีงานที่โพกัสเกี่ยวกับ SUI Developer ด้วยน๊าา ชาร์ตเเบตรอได้เลยจ้า</p>
]]></content:encoded></item><item><title><![CDATA[Sui Stack Messaging SDK: EP 2 First Step Encrypted Chat CLI ]]></title><description><![CDATA[กลับมาตามสัญญาครับ จาก EP ที่แล้วเราได้เรียนรู้ภาพรวมของ Sui Stack Messaging SDK ไปแล้วว่ามันคืออะไร ทำงานยังไง มี 3 layers อะไรบ้าง ใครยังไม่ได้อ่านเบรคไว้ตรงนี้ก่อนเลยไปอ่านกันนะ ไม่งั้นจะงงเออ

htt]]></description><link>https://onthemove.contributiondao.com/sui-stack-messaging-sdk-ep-2-first-step-encrypted-chat-cli</link><guid isPermaLink="true">https://onthemove.contributiondao.com/sui-stack-messaging-sdk-ep-2-first-step-encrypted-chat-cli</guid><category><![CDATA[Sui]]></category><category><![CDATA[onthemoveth]]></category><category><![CDATA[sui-messaging-sdk]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Wed, 15 Apr 2026 05:44:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68b7c67e9e994bcb96821cae/5812e0b5-ea8f-477d-8093-cfaefb110aa5.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>กลับมาตามสัญญาครับ จาก EP ที่แล้วเราได้เรียนรู้ภาพรวมของ Sui Stack Messaging SDK ไปแล้วว่ามันคืออะไร ทำงานยังไง มี 3 layers อะไรบ้าง ใครยังไม่ได้อ่านเบรคไว้ตรงนี้ก่อนเลยไปอ่านกันนะ ไม่งั้นจะงงเออ</p>
<ul>
<li><a href="https://onthemove.contributiondao.com/sui-stack-messaging-sdk-introduction">https://onthemove.contributiondao.com/sui-stack-messaging-sdk-introduction</a></li>
</ul>
<p>ถึงเวลาละ เราจะลงมือสร้าง <strong>CLI Chatroom</strong> ซึ่งจะให้ AI ค่อยโต้ตอบกับเราเเทนตามวิถีคนเป็น introvert (ทำเป็น CLI เพราะตัวฉันไม่ถนัดงาน frontend เลยจ้า) ที่ข้อความทุกอย่างถูก encrypted ด้วย Seal แล้วเก็บบน Sui blockchain โดยจะทำเป็น CLI app ง่ายๆ ที่มีคำสั่งแบบนี้</p>
<ul>
<li><p><code>create-room</code> — สร้างห้องแชท encrypted</p>
</li>
<li><p><code>list-rooms</code> — ดูห้องที่เราอยู่</p>
</li>
<li><p><code>chat</code> — เข้าห้องแชท อ่าน + ส่งข้อความ real-time</p>
</li>
<li><p><code>invite</code> — เชิญคนเข้าห้อง</p>
</li>
<li><p><code>members</code> — ดูสมาชิก</p>
</li>
</ul>
<h3>1. Create project</h3>
<p>ตาม step ครับเริ่มจากสร้าง project กันก่อน</p>
<pre><code class="language-plaintext">mkdir sui-chat-cli &amp;&amp; cd sui-chat-cli
npm init -y
npm i @mysten/messaging @mysten/seal @mysten/sui \
     chalk commander dotenv groq-sdk
npm i -D typescript tsx @types/node
</code></pre>
<p>อย่าลืมเพิ่ม <code>"type": "module"</code> ใน <code>package.json</code> ด้วยนะ แล้วก็เพิ่ม scripts ไว้ใช้สะดวกๆ</p>
<pre><code class="language-json">{
  "type": "module",
  "scripts": {
    "dev": "tsx src/index.ts",
    "bot": "tsx src/bot/bot.ts"
  }
}
</code></pre>
<p>จากนั้นสร้าง <code>.env</code> โดย private key สามารถไป export จาก Slush wallet ได้นะ และเราแนะนำให้แยกกระเป๋าที่ใช้ทดสอบกับกระเป๋าส่วนตัว จะได้ไม่พลาด ซึ่งครั้งใช้บน Testnet เท่านั้น เพราะยังไม่มี Mainnet ให้ใช้งาน</p>
<pre><code class="language-plaintext">SUI_PRIVATE_KEY=suiprivkeyxxxxx
BOT_PRIVATE_KEY=suiprivkeyxxxxx

GROQ_API_KEY=gsk_xxxxx
GROQ_MODEL=llama-3.3-70b-versatile

NETWORK=testnet
BOT_POLL_INTERVAL_MS=5000
</code></pre>
<h3>2. Config</h3>
<pre><code class="language-typescript">import "dotenv/config";
import {
  TESTNET_MESSAGING_PACKAGE_CONFIG,
  MAINNET_MESSAGING_PACKAGE_CONFIG,
} from "@mysten/messaging";

const network = (process.env.NETWORK ?? "testnet") as "testnet" | "mainnet";

export const config = {
  suiPrivateKey: process.env.SUI_PRIVATE_KEY!,
  botPrivateKey: process.env.BOT_PRIVATE_KEY || process.env.SUI_PRIVATE_KEY!,
  network,
  groqApiKey: process.env.GROQ_API_KEY!,
  groqModel: process.env.GROQ_MODEL || "llama-3.3-70b-versatile",
  botPollIntervalMs: parseInt(process.env.BOT_POLL_INTERVAL_MS || "5000"),

  messagingPackageId:
    network === "mainnet"
      ? MAINNET_MESSAGING_PACKAGE_CONFIG.packageId
      : TESTNET_MESSAGING_PACKAGE_CONFIG.packageId,

  get fullnodeUrl(): string {
    return this.network === "mainnet"
      ? "https://fullnode.mainnet.sui.io:443"
      : "https://fullnode.testnet.sui.io:443";
  },

  suiscanTxUrl(digest: string): string {
    return `https://suiscan.xyz/\({this.network}/tx/\){digest}`;
  },

  sealServers: [
    {
      objectId: "0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75",
      weight: 1,
    },
    {
      objectId: "0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8",
      weight: 1,
    },
  ],

  walrus: {
    aggregator: "https://aggregator.walrus-testnet.walrus.space",
    publisher: "https://publisher.walrus-testnet.walrus.space",
    epochs: 1,
  },
};
</code></pre>
<p>ตรง <code>messagingPackageId</code> นี่สำคัญ เพราะถ้า SDK upgrade version ค่านี้อาจเปลี่ยน การดึงจาก SDK โดยตรงทำให้ไม่ต้องมานั่ง update เอง ปัจจุบันมันมีแค่ Testnet นะ ซึ่งมันก็คือ packageId สำหรับส่วนของ messaging นั้นเเหละ ส่วนอื่นๆก็น่าจะเคยเห็นกันมาบ้างแล้วจากบทความก่อนๆ</p>
<h3>3. Messaging Client (สำคัญ)</h3>
<pre><code class="language-typescript">export function createMessagingClient(keypair: Ed25519Keypair) {
  const client = new SuiClient({
    url: config.fullnodeUrl,
    network: config.network,
    mvr: {
      overrides: {
        packages: {
          "@local-pkg/sui-stack-messaging": config.messagingPackageId,
        },
      },
    },
  })
    .$extend(SealClient.asClientExtension({
      serverConfigs: config.sealServers,
    }))
    .$extend(messaging({
      walrusStorageConfig: { /* Walrus endpoints */ },
      sessionKeyConfig: {
        address: keypair.toSuiAddress(),
        ttlMin: 30,
        signer: keypair,
      },
    }));

  return { client, messagingClient: client.messaging, keypair };
}
export function createUserClient() {
  const keypair = createKeypair(config.suiPrivateKey);
  return createMessagingClient(keypair);
}
</code></pre>
<p>มีจุดสำคัญที่เราอาจจะพึ่งเคยเห็นกันก็คือ เราจะใส่ <code>mvr.overrides.packages</code> ไม่งั้นจะเจอ error <code>MVR Api URL is not set</code> ตรงนี้เสียเวลาไป debug อยู่พอสมควร กับอีกส่วนนึงคือ walrus config ที่ต้องใส่ด้วยเพราะตัว SDK มันบังคับถึงจะยังไม่ได้ใช้ก็ตาม</p>
<p><em>MVR คือ Move Registry นะ ไว้ค่อยลงลึกกันอีกทีนึงแต่ฝั่ง npm อาจจะคุ้นๆกันอยู่ละ</em></p>
<h3>4. Encryption Key</h3>
<p>ก่อนจะส่งหรืออ่านข้อความได้ เราต้องมี encryption key ของ channel นั้น ซึ่ง SDK มี method ให้ดึงออกมาจาก channel object บน chain</p>
<pre><code class="language-typescript">export async function getEncryptionKey(
  messagingClient: any,
  channelId: string,
  userAddress: string,
) {
  const channels = await messagingClient.getChannelObjectsByChannelIds({
    channelIds: [channelId],
    userAddress,
  });

  const ch = channels[0];
  return {
    $kind: "Encrypted" as const,
    encryptedBytes: new Uint8Array(ch.encryption_key_history.latest),
    version: ch.encryption_key_history.latest_version,
  };
}
</code></pre>
<p>สิ่งที่ควรรู้คือ encryption key จะ <strong>rotate ทุกครั้งที่เชิญสมาชิกใหม่เข้า channel</strong> ดังนั้นถ้าเราเชิญใครเข้ามาใหม่ key เก่าจะใช้ไม่ได้ ต้อง fetch ใหม่ ส่วนนี้เราสร้างแยกออกมาจะได้เรียกใช้งานได้สะดวก</p>
<h3>5. Create Room — สร้างห้องแชท</h3>
<p>คำสั่งแรกที่เราต้องทำ คือสร้าง encrypted channel</p>
<pre><code class="language-typescript">export async function createRoom(memberAddresses: string[]) {
  const { messagingClient, keypair } = createUserClient();

  const { channelId, creatorCapId } =
    await messagingClient.executeCreateChannelTransaction({
      signer: keypair,
      initialMembers: memberAddresses,
    });

  const memberCap = await messagingClient.getUserMemberCap(
    keypair.toSuiAddress(), channelId
  );

  console.log("✅ Channel ID:", channelId);
}
</code></pre>
<p><code>executeCreateChannelTransaction</code> ทำ 2 อย่างในครั้งเดียว — สร้าง channel + attach encryption key หลังสร้างเสร็จเราจะได้ <code>channelId</code> ที่ต้องเก็บไว้แชร์กับคนอื่น</p>
<p>ส่วน <code>getUserMemberCap</code> คือไปดึง MemberCap ของเรา ซึ่งเป็น object บน chain ที่ prove ว่าเราเป็นสมาชิกห้องนี้ ต้องมี MemberCap ถึงจะส่งข้อความได้</p>
<h3>6. List Rooms &amp; Members</h3>
<p>ดู channels ที่เราเข้าร่วมอยู่ ก็แค่เรียก <code>getChannelMemberships</code></p>
<pre><code class="language-typescript">const result = await messagingClient.getChannelMemberships({
  address: myAddress,
});
</code></pre>
<p>ส่วนดูสมาชิกของ channel ก็ใช้ <code>getChannelMembers</code></p>
<pre><code class="language-typescript">const result = await messagingClient.getChannelMembers(channelId);
</code></pre>
<p>อันนี้จะทำหรือไม่ทำก็ได้ เพราะง่ายมาก SDK จัดการทุกอย่างให้แล้ว</p>
<h3>7. Invite (เอาไว้ดึงคนเข้าห้อง)</h3>
<pre><code class="language-typescript">const { digest, addedMembers } =
  await messagingClient.executeAddMembersTransaction({
    signer: keypair,
    channelId,
    memberCapId: memberCap.id.id,
    newMemberAddresses,
  });
</code></pre>
<blockquote>
<p>⚠️ มีแค่ <strong>creator</strong> (คนที่สร้างห้อง) เท่านั้นที่เชิญคนเข้าได้นะ ถ้าคนอื่นลอง invite จะ error และอย่าลืมว่าเมื่อเชิญสมาชิกใหม่ encryption key จะ rotate — ต้อง fetch key ใหม่ก่อนส่งข้อความ</p>
</blockquote>
<h3>8. Interactive Chat</h3>
<p>มาถึงส่วนสำคัญละ ส่วนนี้จะเป็น interface เอาไว้เราเข้าห้อง chat ผ่าน CLI ซึ่งจะทำให้เราสามารถ ส่ง และอ่านข้อความได้ โดยจะแบ่งเป็น 3 ส่วนหลักๆคือ</p>
<p><strong>ส่วนที่ 1</strong> — ดึง MemberCap, encryption key, และแสดงข้อความเก่า 10 ข้อความย้อนหลัง</p>
<pre><code class="language-typescript">const { messagingClient, keypair } = createUserClient();
const myAddress = keypair.toSuiAddress();
const memberCap = await messagingClient.getUserMemberCap(myAddress, channelId);
const encryptedKey = await getEncryptionKey(messagingClient, channelId, myAddress);

const { messages } = await messagingClient.getChannelMessages({
  channelId,
  userAddress: myAddress,
  limit: 10,
  direction: "backward",
});
</code></pre>
<p><strong>ส่วนที่ 2: Polling</strong> — เช็คข้อความใหม่ทุก 3 วินาที</p>
<p>SDK ไม่มี WebSocket ดังนั้นเราต้อง poll เอง หลักการคือเราจะจำ <code>lastMessageCount</code> ไว้ แล้วถาม SDK ว่ามีข้อความใหม่กว่านี้ไหม ตอนนี้เลยทำเป็น interval ง่ายๆ</p>
<pre><code class="language-typescript">const [channel] = await client.getChannelObjectsByChannelIds({
  channelIds: [channelId], userAddress: myAddress,
});
const pollingState = {
  lastMessageCount: BigInt(channel.messages_count),
  lastCursor: null as bigint | null,
  channelId,
};

setInterval(async () =&gt; {
  const { messages } = await client.getLatestMessages({
    channelId, userAddress: myAddress, pollingState,
  });
  for (const msg of messages) {
    if (msg.sender !== myAddress) printMessage(msg);
  }
  pollingState.lastMessageCount += BigInt(messages.length);
}, 3000);
</code></pre>
<p><strong>ส่วนที่ 3: Input Loop</strong> — ใช้ในการส่งข้อความ</p>
<pre><code class="language-typescript">const rl = createInterface({ input: process.stdin, output: process.stdout });

rl.on("line", async (line) =&gt; {
  if (text === "/quit") { process.exit(0); }

const { digest } = await messagingClient.executeSendMessageTransaction({
    signer: keypair,
    channelId,
    memberCapId: memberCap.id.id,
    message: text,
    encryptedKey,
  });
});
</code></pre>
<p>โดยส่วนนี้เราจะอ่านข้อความจาก cli ซึ่งทุกข้อความที่ส่ง จะถูก encrypt ด้วย Seal → store เป็น object บน Sui → ได้ transaction digest กลับมา ลองไปดูที่ <a href="https://suiscan.xyz/">https://suiscan.xyz/</a> ได้เลย (อย่าลืมเปลี่ยนเป็น Testnet)</p>
<h3>9. CLI Entry Point</h3>
<p>สุดท้ายละทำการรวมทุก command ด้วย Commander</p>
<pre><code class="language-typescript">program.command("create-room")
  .argument("[members...]", "Sui addresses to invite")
  .action(async (members) =&gt; { await createRoom(members || []); });

program.command("list-rooms").action(listRooms);

program.command("chat")
  .argument("&lt;channelId&gt;", "Channel ID")
  .action(interactiveChat);

program.command("invite")
  .argument("&lt;channelId&gt;", "Channel ID")
  .argument("&lt;addresses...&gt;", "Sui addresses to invite")
  .action(inviteMember);

program.command("members")
  .argument("&lt;channelId&gt;", "Channel ID")
  .action(listMembers);
</code></pre>
<h3>9. Testing</h3>
<p>เมื่อประกอบร่างทุกอย่างแล้ว ลองรันทดสอบกันดูนะ</p>
<pre><code class="language-bash"># Help command
npm run dev -- --help

# Create room
npm run dev -- create-room

# Create room and invite
npm run dev -- create-room 0xALICE_ADDRESS

# List all rooms
npm run dev -- list-rooms

# Join chat room 
npm run dev -- chat &lt;channelId&gt;

# List all members
npm run dev -- members &lt;channelId&gt;

# Invite bot to chat room
npm run dev -- invite &lt;channelId&gt; &lt;BOT_ADDRESS&gt;
</code></pre>
<p>เรียบร้อยเราได้ตัว client แล้วเเต่.... ยังไม่จบนะ มาได้เกินครึ่งทางละ ไว้ต่อ EP หน้า ซึ่งเป็น EP สุดท้ายละ ตัว EP หน้าเราจะมาต่อกับ AI โดยให้ AI ตอบโต้กับคนในห้อง ซึ่งเราเลือกใช้ Groq AI มันมี Free API key อยู่ด้วย หรือใครจะใช้ตัวอื่นๆก็เตรียมๆไว้ได้เลย เพราะส่วนนี้เเค่เป็นการเขียน API เชื่อมต่อกับ AI เท่านั้น</p>
<p>เจอกันจ้า</p>
]]></content:encoded></item><item><title><![CDATA[Sui Stack Messaging SDK : Introduction]]></title><description><![CDATA[ตามที่สัญญาเอาไว้หลังจากหายไปนาน ติดภารกิจต่อเนื่องเลย วันนี้เราจะมากับหัวข้อ SUI Stack Messaging ซึ่งเป็น SDK ตัวใหม่ที่ Mysten Labs ปล่อยออกมาตั้งแต่เดือน กันยายน 2025 (ตอนนี้เวอร์ชันล่าสุดคือ v0.3.]]></description><link>https://onthemove.contributiondao.com/sui-stack-messaging-sdk-introduction</link><guid isPermaLink="true">https://onthemove.contributiondao.com/sui-stack-messaging-sdk-introduction</guid><category><![CDATA[Sui]]></category><category><![CDATA[onthemoveth]]></category><category><![CDATA[walrus]]></category><category><![CDATA[Seal]]></category><category><![CDATA[sui-messaging-sdk]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Tue, 14 Apr 2026 12:05:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68b7c67e9e994bcb96821cae/ccae891a-8e09-402d-bbd2-989d9aa21ef7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>ตามที่สัญญาเอาไว้หลังจากหายไปนาน ติดภารกิจต่อเนื่องเลย วันนี้เราจะมากับหัวข้อ SUI Stack Messaging ซึ่งเป็น SDK ตัวใหม่ที่ Mysten Labs ปล่อยออกมาตั้งแต่เดือน กันยายน 2025 (ตอนนี้เวอร์ชันล่าสุดคือ v0.3.0) โดย SDK ตัวนี้ไม่ใช่แค่ messaging library ทั่วไปนะ แต่เป็นการทำให้ messaging กลายเป็นส่วนหนึ่งของ <strong>Sui Stack</strong> เลย ทำงานร่วมกับ Sui, Walrus, Seal แบบ native เลยเเหละ</p>
<p><em>ตอนนี้ SDK ยังอยู่ในสถานะ</em> <em><strong>Alpha</strong></em> <em>และรองรับเฉพาะ</em> <em><strong>Testnet</strong></em> <em>เท่านั้นนะ ยังไม่พร้อมใช้ production นะ</em></p>
<h3>Sui Stack Messaging SDK คืออะไร?</h3>
<p>พูดง่ายๆ มันคือชุดเครื่องมือที่ให้ developer สามารถฝัง <strong>encrypted messaging</strong> เข้าไปใน Web 3.0 application ได้เลย โดยไม่ต้องสร้าง backend เอง ข้อความทุกอย่างจะถูกเข้ารหัสแบบ end-to-end ผูกกับ wallet address และ recoverable ข้ามอุปกรณ์ได้</p>
<p>. .</p>
<p>ที่น่าสนใจคือ SDK ถูกออกแบบให้ทำงานร่วมกับ 3 layers ของ Sui Stack ดังนี้</p>
<p><strong>1) Sui Blockchain — Identity &amp; State</strong></p>
<p>ทุกช่องทาง, ข้อความ, และการเป็นสมาชิกถูกจัดเก็บเป็นวัตถุบน Sui blockchain โดยใช้สัญญาอัจฉริยะ Move ในการจัดการ สิ่งที่น่าสนใจคือ SDK ใช้การควบคุมการเข้าถึงแบบอิงตามความสามารถ</p>
<ul>
<li><p>CreatorCap — ผู้สร้างช่องทางจะได้รับวัตถุนี้ ใช้สำหรับเพิ่ม/ลบสมาชิก มีเพียงหนึ่งคนต่อช่องทาง</p>
</li>
<li><p>MemberCap — สมาชิกทุกคนจะได้รับวัตถุนี้ ใช้เป็นหลักฐานการเป็นสมาชิกสำหรับการส่งข้อความ</p>
</li>
</ul>
<p>ถ้าใครเขียน Move มาบ้างจะคุ้นเคยกับ pattern นี้ดี เพราะมัน object-centric เหมือนทุกอย่างบน Sui เลย</p>
<p><strong>2) Walrus — Decentralized Attachment Storage</strong></p>
<p>ไฟล์แนบทั้งหมด (รูป, เอกสาร ฯลฯ) จะถูก encrypt ด้วย Seal ก่อน แล้ว upload ไปเก็บบน Walrus ครับ ส่วน metadata กับ reference จะถูกบันทึกบน Sui เพื่อให้ verifiable ได้ การเข้าถึงไฟล์ใช้ผ่าน aggregator/publisher endpoints ของ Walrus</p>
<p><strong>3) Seal — Encryption &amp; Access Control</strong></p>
<p>Seal ทำหน้าที่เป็น key management layer เลย เมื่อสร้าง channel จะมีการ generate symmetric key ขึ้นมา แล้ว encrypt ด้วย Seal ก่อนผูกกับ channel object บน Sui</p>
<p>ที่ทำให้มันต่างจาก encryption ทั่วไปคือ <strong>programmable access policies</strong> สามารถกำหนดเงื่อนไขการเข้าถึงผ่าน smart contract ได้เลย เช่น ต้องถือ token จำนวนหนึ่ง หรือเป็นสมาชิก DAO ถึงจะอ่านข้อความได้ ตามที่เราเคยเขียนไปเเล้วนั้นเองในบทความก่อนหน้านี้</p>
<img src="https://cdn.hashnode.com/uploads/covers/68b7c67e9e994bcb96821cae/0ec9220d-fa51-466a-8dee-e7001516195e.png" alt="" style="display:block;margin:0 auto" />

<p><strong>การทำงานของ Encryption</strong></p>
<p>มาดูทบทวนกันว่า Seal ทำงานยังไงกับ SDK ตัวนี้ ขั้นตอนคร่าวๆ คือ</p>
<ol>
<li><p>สร้าง Channel → SDK สร้าง channel object บน Sui พร้อม MemberCaps ให้สมาชิก</p>
</li>
<li><p>Generate Encryption Key → Seal generate symmetric key แล้ว encrypt ไว้</p>
</li>
<li><p>Attach Key กับ Channel → key ถูกผูกกับ channel object บน chain</p>
</li>
<li><p>ส่งข้อความ → ข้อความถูก encrypt ด้วย key แล้ว store เป็น object บน Sui</p>
</li>
<li><p>อ่านข้อความ → SDK fetch message objects แล้ว decrypt ด้วย Session Key</p>
</li>
</ol>
<p><em>Session Key คืออะไร? ก็คือ key ที่มีอายุจำกัด (TTL) เพื่อไม่ต้อง sign transaction ทุกครั้งที่ decrypt ข้อความ sign ครั้งเดียวสร้าง session แล้วใช้ต่อเนื่องได้เลย เมื่อหมดอายุ SDK จะ refresh ให้อัตโนมัติ</em></p>
<p>เปรียบเทียบกับ XMTP และ Push Protocol (คู่แข่ง)</p>
<img src="https://cdn.hashnode.com/uploads/covers/68b7c67e9e994bcb96821cae/4293c944-d77d-416f-bd66-1946a177d205.png" alt="" style="display:block;margin:0 auto" />

<p>จุดแข็งที่สุดของ Sui Messaging SDK คือ on-chain composability นะ messages เป็น Sui objects ที่ smart contracts สามารถ interact ได้โดยตรง ซึ่ง XMTP หรือ Push ทำไม่ได้ในระดับเดียวกัน แต่ข้อแลกเปลี่ยนคือต้องผูกกับ Sui ecosystem</p>
<p><strong>สรุปข้อจำกัดที่ควรรู้</strong></p>
<ul>
<li><p>ไม่มี Anonymous Messaging — ทุกข้อความต้องมา verified Sui identity ไม่สามารถส่งแบบ anonymous ได้</p>
</li>
<li><p>ไม่มี Forward Secrecy — ถ้า key ถูก compromise ข้อความในอดีตก็อาจถูก decrypt ได้</p>
</li>
<li><p>Polling-based — ยังไม่มี WebSocket ต้องใช้ <code>getLatestMessages()</code> แบบ polling</p>
</li>
<li><p>Testnet Only — ยังไม่พร้อม Mainnet อาจมี breaking changes ไว้ว่ากันทีเมื่อถึงตอนนั้น</p>
</li>
</ul>
<hr />
<p>เป็นไงบ้างเห็นภาพรวมกันแล้วใช่ไหม สรุปง่ายๆ Sui Stack Messaging SDK มันทำให้ messaging กลายเป็น <strong>programmable primitive</strong> ของ Web 3.0 application บน Sui ได้ ไม่ใช่แค่ chat library ธรรมดา แต่เป็น infrastructure ที่ smart contracts สามารถ trigger ได้เลย</p>
<p>เดียว EP หน้าจะมาลงมือเขียนโค้ดกันจริงๆ ตั้งแต่ setup project, สร้าง channel, ส่งข้อความ, อ่านข้อความ แบบ step-by-step เลยนะ รอบนี้ไม่ให้รอนานละ ดองไว้นานเกินต้องขอโทษด้วยนะครับ</p>
]]></content:encoded></item><item><title><![CDATA[DeepBook: Placing Orders & Swap EP 3]]></title><description><![CDATA[กลับมาตามสัญญากับ EP สุดท้ายสำหรับ ซีรีย์ DeepBook ใครยังไม่ได้ตาม EP ก่อนหน้า เบรคไว้ตรงนี้ก่อนเลย ไปอ่านกันนะ ไม่งั่นทำไม่ได้นะเออ

https://onthemove.contributiondao.com/deepbook-ep1

https://onthemove.contributiondao.com/deepbook-ep2


ก่อนหน้านี้...]]></description><link>https://onthemove.contributiondao.com/deepbook-ep3</link><guid isPermaLink="true">https://onthemove.contributiondao.com/deepbook-ep3</guid><category><![CDATA[onthemoveth]]></category><category><![CDATA[Sui]]></category><category><![CDATA[deepbookv3]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Fri, 13 Feb 2026 12:21:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770985659339/cdc27d9e-f42e-42cd-9144-e340fe322a28.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>กลับมาตามสัญญากับ EP สุดท้ายสำหรับ ซีรีย์ DeepBook ใครยังไม่ได้ตาม EP ก่อนหน้า เบรคไว้ตรงนี้ก่อนเลย ไปอ่านกันนะ ไม่งั่นทำไม่ได้นะเออ</p>
<ul>
<li><p><a target="_blank" href="https://onthemove.contributiondao.com/deepbook-ep1">https://onthemove.contributiondao.com/deepbook-ep</a>1</p>
</li>
<li><p><a target="_blank" href="https://onthemove.contributiondao.com/deepbook-ep2">https://onthemove.contributiondao.com/deepbook-ep2</a></p>
</li>
</ul>
<p>ก่อนหน้านี้เราได้เรียนรู้วิธีการ Create balance manager, Deposit, Check balance และ Withdraw ไปแล้วใช่ไหม มาวันนี้ได้เวลาละกับการวาง Order ซึ่งง่ายมากเมื่อทำผ่าน SDK พร้อมละเริ่มกันเลย</p>
<hr />
<ol>
<li><p><strong>Create limit order</strong></p>
<p> เริ่มจากเตรียมข้อมูลต่างๆก่อน ซึ่งก็เหมือนครั้งที่แล้วเลย</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">import</span> <span class="hljs-string">"dotenv/config"</span>;
 <span class="hljs-keyword">import</span> { deepbook, <span class="hljs-keyword">type</span> BalanceManager } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/deepbook-v3"</span>;
 <span class="hljs-keyword">import</span> { SuiGrpcClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/grpc"</span>;
 <span class="hljs-keyword">import</span> { decodeSuiPrivateKey } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/cryptography"</span>;
 <span class="hljs-keyword">import</span> { Ed25519Keypair } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/keypairs/ed25519"</span>;
 <span class="hljs-keyword">import</span> { Transaction } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/transactions"</span>;

 <span class="hljs-keyword">const</span> env = (process.env.NETWORK ?? <span class="hljs-string">"testnet"</span>) <span class="hljs-keyword">as</span> <span class="hljs-string">"testnet"</span> | <span class="hljs-string">"mainnet"</span>;
 <span class="hljs-keyword">const</span> { scheme, secretKey } = decodeSuiPrivateKey(process.env.SUI_PRIVATE_KEY!);
 <span class="hljs-keyword">if</span> (scheme !== <span class="hljs-string">"ED25519"</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Unsupported scheme: <span class="hljs-subst">${scheme}</span>`</span>);
 <span class="hljs-keyword">const</span> keypair = Ed25519Keypair.fromSecretKey(secretKey);
 <span class="hljs-keyword">const</span> address = keypair.toSuiAddress();

 <span class="hljs-keyword">const</span> BALANCE_MANAGER_KEY = <span class="hljs-string">"BM1"</span>;
 <span class="hljs-keyword">const</span> BALANCE_MANAGER_ID = <span class="hljs-string">"0x09921a24e9fe239b194a109ed8b8812f86fefc3389f851c4d06f7e55bef79a9f"</span>;
 <span class="hljs-keyword">const</span> balanceManagers: { [k: <span class="hljs-built_in">string</span>]: BalanceManager } = {
   [BALANCE_MANAGER_KEY]: { address: BALANCE_MANAGER_ID },
 };
 <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> SuiGrpcClient({
   network: env,
   baseUrl: env === <span class="hljs-string">"mainnet"</span> ? <span class="hljs-string">"https://fullnode.mainnet.sui.io:443"</span> : <span class="hljs-string">"https://fullnode.testnet.sui.io:443"</span>,
 }).$extend(
   deepbook({
     address,
     balanceManagers,
   }),
 );
 <span class="hljs-keyword">const</span> POOL_KEY = <span class="hljs-string">"SUI_DBUSDC"</span>;
</code></pre>
<p> ทำการตรวจสอบ Bid และ Ask order ว่ามีการวางคำสั่งซื้อขายที่ราคาเท่าไหร่</p>
<pre><code class="lang-typescript"> <span class="hljs-comment">//Query Bid, Ask order</span>
 <span class="hljs-keyword">const</span> bids = <span class="hljs-keyword">await</span> client.deepbook.getLevel2Range(POOL_KEY, <span class="hljs-number">0.9</span>, <span class="hljs-number">0.91</span>, <span class="hljs-literal">true</span>);
 <span class="hljs-keyword">const</span> asks = <span class="hljs-keyword">await</span> client.deepbook.getLevel2Range(POOL_KEY, <span class="hljs-number">0.91</span>, <span class="hljs-number">0.95</span>, <span class="hljs-literal">false</span>);

 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"=== BIDS Order ==="</span>);
 <span class="hljs-built_in">console</span>.dir(bids, { depth: <span class="hljs-literal">null</span> });

 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"=== ASKS Order ==="</span>);
 <span class="hljs-built_in">console</span>.dir(asks, { depth: <span class="hljs-literal">null</span> });
</code></pre>
<p> ซึ่งเราจะเรียกผ่าน <code>getLevel2Range</code> นั้นเอง ซึ่งสิ่งที่เราต้องใช้เพื่อเรียกดูข้อมูลคือ Pool ที่เราต้องการเรียกดูข้อมูล เเละช่วงของราคาซึ่งจะเป็น ช่วงที่ต่ำสุดเเละสูงที่สุด รวมถึง Boolean ซึ่งจะบ่งบอกว่าจะดึงข้อมูลฝั่งไหนระหว่าง Bid หรือ Ask เมื่อเราทราบช่วงราคาเเล้ว หรือจะไม่ต้องสนใจก็ได้เผื่อใครอยากวางช้อนซื้อลึกๆ หรือขายสูง ก็ปล่อยผ่านไปเลย</p>
<p> ขั้นตอนต่อเราก็ต้องสร้างคำสั่งขาย SUI ของเรา ด้วยวิธีการตามด้านล่างนี้</p>
<pre><code class="lang-typescript"> <span class="hljs-comment">//Check SUI Balance</span>
 <span class="hljs-keyword">const</span> suiInManager = <span class="hljs-keyword">await</span> client.deepbook.checkManagerBalance(BALANCE_MANAGER_KEY, <span class="hljs-string">"SUI"</span>);
 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"SUI in manager ="</span>, suiInManager);

 <span class="hljs-keyword">const</span> tx = <span class="hljs-keyword">new</span> Transaction();

 <span class="hljs-keyword">const</span> expireTimestamp = <span class="hljs-built_in">Date</span>.now() + <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>; <span class="hljs-comment">//1 HR</span>
 <span class="hljs-comment">//Verify check if an order can be placed</span>
 <span class="hljs-keyword">const</span> checkCanPlaceOrder = <span class="hljs-keyword">await</span> client.deepbook.canPlaceLimitOrder({
   poolKey: <span class="hljs-string">"SUI_DBUSDC"</span>,
   balanceManagerKey: <span class="hljs-string">"BM1"</span>,
   price: <span class="hljs-number">1</span>,
   quantity: <span class="hljs-number">1.0</span>,
   isBid: <span class="hljs-literal">false</span>, <span class="hljs-comment">// false = sell</span>
   payWithDeep: <span class="hljs-literal">false</span>, <span class="hljs-comment">//Pay </span>
   expireTimestamp,
 });
 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"canPlaceLimitOrder = "</span>, checkCanPlaceOrder);

 tx.add(
   client.deepbook.deepBook.placeLimitOrder({
     poolKey: <span class="hljs-string">"SUI_DBUSDC"</span>,
     balanceManagerKey: <span class="hljs-string">"BM1"</span>,
     clientOrderId: <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">Date</span>.now()}</span>`</span>,
     price: <span class="hljs-number">1</span>,
     quantity: <span class="hljs-number">5.0</span>,
     isBid: <span class="hljs-literal">false</span>,
     payWithDeep: <span class="hljs-literal">false</span>,
   }),
 );

 <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> client.core.signAndExecuteTransaction({
   transaction: tx,
   signer: keypair,
   include: { effects: <span class="hljs-literal">true</span>, objectTypes: <span class="hljs-literal">true</span> },
 });

 <span class="hljs-comment">//Get account information</span>
 <span class="hljs-keyword">let</span> acct = <span class="hljs-keyword">await</span> client.deepbook.account(POOL_KEY, BALANCE_MANAGER_KEY);
 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"=== ACCOUNT (available/locked) ==="</span>);
 <span class="hljs-built_in">console</span>.dir(acct);
</code></pre>
<p> จากตัวอย่างนี้เราจะเริ่มจากตรวจก่อนว่าเราสามารถวาง Order ได้ไหม เช่นถ้าเรามี SUI ไม่พอใน Balance Manager ก็จะไม่สามารถวาง Order ได้นั้นเอง เเละขั้นตอนสุดท้ายเราจะเรียกดูของมูลของเราขึ้นมา ซึ่งนึงที่จะได้คือ Order ID นั้นเอง โดย Order ID ห้ามซ้ำนะ เราเลยใช้ timestamp เลย ส่วน <code>payWithDeep</code> คือใช้ DEEP Token จ่ายเป็นค่าธรรมเนียมซึ่งเราไม่มีเลยใช้ SUI จ่ายแทน</p>
<p> ง่ายไหม เราวาง Order ได้เรียบร้อยแล้วก็รอให้ Order ของเราถูก Match ก็เรียบร้อย</p>
</li>
<li><p><strong>Cancel order</strong></p>
<p> การยกเลิก Order นั้นสามารถทำผ่าน SDK ได้เลยครับ มีตั้งแต่คำสั่งยกเลิกทีละ Order หรือยกเลิกทั้งหมดก็ได้ วิธีการก็มีดังนี้</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">const</span> cancelTx = <span class="hljs-keyword">new</span> Transaction();
 cancelTx.add(client.deepbook.deepBook.cancelAllOrders(POOL_KEY, BALANCE_MANAGER_KEY));

 <span class="hljs-keyword">const</span> resCancelOrder = <span class="hljs-keyword">await</span> client.core.signAndExecuteTransaction({
   transaction: cancelTx,
   signer: keypair,
   include: { effects: <span class="hljs-literal">true</span>, objectTypes: <span class="hljs-literal">true</span> },
 });
</code></pre>
<p> ถ้าหากเราต้องการยกเลิกทีละ Order ก็เปลี่ยนไปใช้งานฟังก์ชั่น <code>cancelOrder</code> หรือ <code>cancelOrders</code> นะ โดยเราสามารถนำ Order ID จากขั้นตอนที่แล้วมาใช้ได้เลย หลังจากนั้นลองเรียก <code>deepbook.account</code> ดูนะว่า Order ของเราได้ถูกยกเลิกไปหรือไม่</p>
</li>
<li><p><strong>Market order</strong></p>
<p> มีวาง Limit ไปแล้วก็ขาดเรื่องการวางเเบบ Market order ไม่ได้ ซึ่งเราได้อธิบายไปตั้งแต่ EP แรกเเล้วว่าขั้นตอนการทำงานของ Market order เป็นยังไง ลองดูนะ วิธิการก็ง่ายอีกเช่นกัน</p>
<pre><code class="lang-typescript"> tx.add(
   client.deepbook.deepBook.placeMarketOrder({
     poolKey: POOL_KEY,
     balanceManagerKey: BALANCE_MANAGER_KEY,
     clientOrderId: <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">Date</span>.now()}</span>`</span>,
     quantity:<span class="hljs-number">1</span>,
     isBid: <span class="hljs-literal">false</span>,
     payWithDeep: <span class="hljs-literal">false</span>,
   }),
 );
</code></pre>
<p> ในกรณีตัวอย่างข้างต้นคือขาย SUI แบบ Market order เป็นจำนวน 1 SUI</p>
</li>
<li><p><strong>Swap token</strong></p>
<p> ส่วนนี้จะต่างจากการวาง Order นะ ซึ่งจำได้ไหมจาก EP ที่เเล้วเราต้องเริ่มจาก Deposit เงินที่ต้องการเทรดลงไปใน Balance manager จึงจะสามารถทำการสร้าง Order ได้ แต่กรณีของ Swap นั้นจะเเตกต่างกัน เพราะมีให้เลือกว่าจะใช้หรือไม่ใช้ ถ้าไม่ใช้ก็จะเป็นการ Swap โดยดูจาก token ที่อยู่ใน Wallet เราได้เลย ถ้าใครเคยใช้งานพวก Uniswap ก็น่าจะคุ้นเคยดี โดย DeepBook ได้คำสั่งสำหรับการ Swap ทั้งหมดดังนี้</p>
<ul>
<li><p>swapExactBaseForQuote ระบุจำนวน Base token ที่ต้องการขาย แล้วระบบจะคำนวณให้ว่าได้ Quote เท่าไหร่</p>
</li>
<li><p>swapExactQuoteForBase ระบุจำนวน Quote token ที่ต้องการใช้ซื้อ แล้วระบบจะคำนวณว่าได้ Base เท่าไหร่</p>
</li>
<li><p>swapExactQuantity ใช้สำหรับ swap จำนวนที่กำหนด (exact quantity) ได้ทั้งสองทิศทาง</p>
</li>
<li><p>swapExactBaseForQuoteWithManager, swapExactQuoteForBaseWithManager, swapExactQuantityWithManager เหมือน ข้างบนแต่ใช้จาก Balance Manager</p>
</li>
</ul>
</li>
</ol>
<p>    ซึ่งแน่นอนเราจะไม่ใช้จาก Balance Manager มาดูวิธีกันเลย</p>
<pre><code class="lang-typescript">    <span class="hljs-keyword">const</span> swapTx = <span class="hljs-keyword">new</span> Transaction();

    <span class="hljs-keyword">const</span> [baseOut, quoteOut, deepOut] = client.deepbook.deepBook.swapExactBaseForQuote({
        poolKey: <span class="hljs-string">"SUI_DBUSDC"</span>,
        amount: <span class="hljs-number">1</span>,       
        deepAmount: <span class="hljs-number">1</span>,  
        minOut: <span class="hljs-number">0.5</span>   
    })(swapTx);
    swapTx.transferObjects([baseOut, quoteOut, deepOut], keypair.toSuiAddress());

    <span class="hljs-keyword">const</span> swapRes = <span class="hljs-keyword">await</span> client.core.signAndExecuteTransaction({
        transaction: swapTx,
        signer: keypair,
        include: { effects: <span class="hljs-literal">true</span>, events: <span class="hljs-literal">true</span>, objectTypes: <span class="hljs-literal">true</span> },
    });
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"=== SWAP RESULT ==="</span>);
    <span class="hljs-built_in">console</span>.dir(swapRes, { depth: <span class="hljs-literal">null</span> });
</code></pre>
<p>    จากตัวอย่างก็คือเราจะ SWAP 1 SUI ไปเป็น DBUSDC นั้นเอง แต่จะมีปัญหา เพราะว่า…เราไม่มี DEEP Token ใช้ไหม วิธีการก็ไม่ยากลองเอาทั้งหมดที่เรียนรู้มาลองดูนะ ไบ้ให้ว่าก็ทำลองทำพวก Market order แล้วถอนออกมายังกระเป๋าของเราเอง เราก็จะได้ DEEP Token สำหรับจ่ายค่าธรรมเนียมแล้ว</p>
<p>เป็นไงหวังว่าจะเห็นภาพรวมทั้งหมดนะ สมัยนี้อะไรๆก็ Build ง่ายยย เพราะเครื่องมือไม้เครื่องมือพร้อมไปหมดละ เเละถ้าใครคันมือลองไปลองไปประลองฝีมือได้ที่นี้ <a target="_blank" href="https://x.com/SuiNetwork/status/2021269860211400801">https://x.com/SuiNetwork/status/2021269860211400801</a> นะกับโครงการ DeFi Moonshoot ซึ่งมี incentive ให้สูงถึง $500k ด้วยกัน</p>
<p>เอาละไว้เจอกันในบทความหน้า เอาเรื่องอะไรดี เเล้วก็อย่าลืมมาเรียนกันนะกับ กับโครงการ <code>ON THE MOVE: Getting Started with Move on Sui</code></p>
<p>สมัครเลยที่นี้จ้า <a target="_blank" href="https://luma.com/j743avm7">https://luma.com/j743avm7</a></p>
<p>มากินหมูสะเต๊ะกัน</p>
<p>เจอกันนะ</p>
]]></content:encoded></item><item><title><![CDATA[DeepBook: Understanding BalanceManager EP 2]]></title><description><![CDATA[ขอโทษที่ล่าช้าครับ เริ่มไม่มั่นใจละว่าที่ลงช้า เพราะ ป่วย หรืองานเยอะ จนขี้เกียจ หรือป่วยการเมือง เเต่มาช้าก็มานะ วันนี้เราจะมาตามสัญญา ถึงเวลาลงมือเขียนโค๊ดกันเเหละ โดยเราจะเริ่มกันที่ Balance Manager ก่อน ทบทวนกันอีกนิด จากบทความที่แล้ว อย่าลืมไปอ่...]]></description><link>https://onthemove.contributiondao.com/deepbook-ep2</link><guid isPermaLink="true">https://onthemove.contributiondao.com/deepbook-ep2</guid><category><![CDATA[Sui]]></category><category><![CDATA[onthemoveth]]></category><category><![CDATA[deepbookv3]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Mon, 09 Feb 2026 15:26:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770650995647/58910193-03b1-43cd-8dc5-60fbb2999a82.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>ขอโทษที่ล่าช้าครับ เริ่มไม่มั่นใจละว่าที่ลงช้า เพราะ ป่วย หรืองานเยอะ จนขี้เกียจ หรือป่วยการเมือง เเต่มาช้าก็มานะ วันนี้เราจะมาตามสัญญา ถึงเวลาลงมือเขียนโค๊ดกันเเหละ โดยเราจะเริ่มกันที่ Balance Manager ก่อน ทบทวนกันอีกนิด จากบทความที่แล้ว อย่าลืมไปอ่านกันก่อนนะ จะได้เข้าใจว่า Balance Manager คืออะไร</p>
<ul>
<li><a target="_blank" href="https://onthemove.contributiondao.com/deepbook-ep1">https://onthemove.contributiondao.com/deepbook-ep1</a></li>
</ul>
<p>พร้อมแล้วก็มาเริ่มกันเลย</p>
<hr />
<ol>
<li><p><strong>Create project and setup configuration</strong></p>
<pre><code class="lang-typescript"> mkdir deepbook-basic &amp;&amp; cd deepbook-basic
 npm init -y
 npm i <span class="hljs-meta">@mysten</span>/sui <span class="hljs-meta">@mysten</span>/deepbook-v3 dotenv
 npm i -D typescript tsx <span class="hljs-meta">@types</span>/node
</code></pre>
<p> สร้าง .env โดย private key สามารถไป export จาก Slush wallet ได้นะครับ และเราแนะนำให้เเยกกระเป๋าที่ใช้ทดสอบกับกระเป๋าส่วนตัวนะ จะได้ไม่พลาด</p>
<pre><code class="lang-typescript"> SUI_PRIVATE_KEY=suiprivkeyxxxxx
 NETWORK=testnet
</code></pre>
<p> เราจะทดสอบกันที่ Testnet อย่าลืมไปขอ Faucet SUI testnet token ด้วยนะ เพราะนอกจากจะต้องใช้เพื่อจ่ายค่าทำธุรกรรมแล้ว เราจะต้องใช้เพื่อฝากเข้าไปยัง BalanceManager ด้วย</p>
</li>
<li><p><strong>Create Balance Manager</strong></p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">import</span> <span class="hljs-string">"dotenv/config"</span>;
 <span class="hljs-keyword">import</span> { deepbook, <span class="hljs-keyword">type</span> DeepBookClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/deepbook-v3"</span>;
 <span class="hljs-keyword">import</span> { SuiGrpcClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/grpc"</span>;
 <span class="hljs-keyword">import</span> { decodeSuiPrivateKey } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/cryptography"</span>;
 <span class="hljs-keyword">import</span> { Ed25519Keypair } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/keypairs/ed25519"</span>;
 <span class="hljs-keyword">import</span> { Transaction } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/transactions"</span>;

 <span class="hljs-keyword">const</span> env = (process.env.NETWORK ?? <span class="hljs-string">"testnet"</span>) <span class="hljs-keyword">as</span> <span class="hljs-string">"testnet"</span> | <span class="hljs-string">"mainnet"</span>;
 <span class="hljs-keyword">const</span> { scheme, secretKey } = decodeSuiPrivateKey(process.env.SUI_PRIVATE_KEY!);
 <span class="hljs-keyword">if</span> (scheme !== <span class="hljs-string">"ED25519"</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Unsupported scheme: <span class="hljs-subst">${scheme}</span>`</span>);
 <span class="hljs-keyword">const</span> keypair = Ed25519Keypair.fromSecretKey(secretKey);
 <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> SuiGrpcClient({
   network: env,
   baseUrl: env === <span class="hljs-string">"mainnet"</span> ? <span class="hljs-string">"https://fullnode.mainnet.sui.io:443"</span> : <span class="hljs-string">"https://fullnode.testnet.sui.io:443"</span>,
 }).$extend(
   deepbook({
     address: keypair.toSuiAddress(),
   }),
 );

 <span class="hljs-keyword">const</span> tx = <span class="hljs-keyword">new</span> Transaction();
 tx.add(client.deepbook.balanceManager.createAndShareBalanceManager());

 <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> client.core.signAndExecuteTransaction({
   transaction: tx,
   signer: keypair,
   include: { effects: <span class="hljs-literal">true</span>, objectTypes: <span class="hljs-literal">true</span> },
 });

 <span class="hljs-built_in">console</span>.dir(result, { depth: <span class="hljs-literal">null</span> });
</code></pre>
<p> ขั้นตอนนี้เราจะได้ <code>BalanceManager objectId</code> สำคัญให้เก็บเอาไว้นะครับ โดยการสร้าง Balance Manager จะมีทั้งหมด 3 ประเภทด้วยกันคือ</p>
<ul>
<li><p>createAndShareBalanceManager สร้าง + เเชร์ ในขั้นตอนเดียว เอาไว้ใช้งานทั่วไป</p>
</li>
<li><p>createBalanceManagerWithOwner สร้างแบบ Private เอาไว้ควบคุมหรือเตรียม object ก่อนจะเเชร์ภายหลัง</p>
</li>
<li><p>shareBalanceManager เอาไว้เเชร์จากขั้นตอน createBalanceManagerWithOwner นั้นเอง</p>
</li>
</ul>
</li>
</ol>
<p>    และตัวอย่างของเราจะได้เป็นเเบบนี้</p>
<pre><code class="lang-plaintext">    '0x09921a24e9fe239b194a109ed8b8812f86fefc3389f851c4d06f7e55bef79a9f': '0xfb28c4cbc6865bd1c897d26aecbe1f8792d1509a20ffec692c800660cbec6982::balance_manager::BalanceManager'
</code></pre>
<ol start="3">
<li><p><strong>Deposit SUI into Balance Manager</strong></p>
<p> หลังจากที่เราสร้าง Balance Manager เรียบร้อยแล้ว เราก็จะต้องทำการฝาก SUI testnet token เข้าไปยัง Balance MAnager ของเรา จริงๆมีหลายเหรียญให้ใช้ทดสอบ เช่น DEEP, WAL เพียงแต่มันจะวุ่นวายหน่อย เพราะหา testnet token ลำบากเลยใช้ SUI testnet token ไปเลย ง่ายดี</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">import</span> <span class="hljs-string">"dotenv/config"</span>;
 <span class="hljs-keyword">import</span> { deepbook, <span class="hljs-keyword">type</span> BalanceManager } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/deepbook-v3"</span>;
 <span class="hljs-keyword">import</span> { SuiGrpcClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/grpc"</span>;
 <span class="hljs-keyword">import</span> { decodeSuiPrivateKey } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/cryptography"</span>;
 <span class="hljs-keyword">import</span> { Ed25519Keypair } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/keypairs/ed25519"</span>;
 <span class="hljs-keyword">import</span> { Transaction } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/transactions"</span>;

 <span class="hljs-keyword">const</span> env = (process.env.NETWORK ?? <span class="hljs-string">"testnet"</span>) <span class="hljs-keyword">as</span> <span class="hljs-string">"testnet"</span> | <span class="hljs-string">"mainnet"</span>;
 <span class="hljs-keyword">const</span> { scheme, secretKey } = decodeSuiPrivateKey(process.env.SUI_PRIVATE_KEY!);
 <span class="hljs-keyword">if</span> (scheme !== <span class="hljs-string">"ED25519"</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Unsupported scheme: <span class="hljs-subst">${scheme}</span>`</span>);
 <span class="hljs-keyword">const</span> keypair = Ed25519Keypair.fromSecretKey(secretKey);

 <span class="hljs-keyword">const</span> BALANCE_MANAGER_KEY = <span class="hljs-string">"BM1"</span>;
 <span class="hljs-keyword">const</span> BALANCE_MANAGER_ID = <span class="hljs-string">"0x09921a24e9fe239b194a109ed8b8812f86fefc3389f851c4d06f7e55bef79a9f"</span>;

 <span class="hljs-keyword">const</span> balanceManagers: { [k: <span class="hljs-built_in">string</span>]: BalanceManager } = { [BALANCE_MANAGER_KEY]: { address: BALANCE_MANAGER_ID } };

 <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> SuiGrpcClient({
   network: env,
   baseUrl: env === <span class="hljs-string">"mainnet"</span> ? <span class="hljs-string">"https://fullnode.mainnet.sui.io:443"</span> : <span class="hljs-string">"https://fullnode.testnet.sui.io:443"</span>,
 }).$extend(
   deepbook({
     address: keypair.toSuiAddress(),
     balanceManagers,
   }),
 );
 <span class="hljs-keyword">const</span> tx = <span class="hljs-keyword">new</span> Transaction();
 tx.add(client.deepbook.balanceManager.depositIntoManager(BALANCE_MANAGER_KEY, <span class="hljs-string">"SUI"</span>, <span class="hljs-number">0.5</span>));

 <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> client.core.signAndExecuteTransaction({
   transaction: tx,
   signer: keypair,
   include: { effects: <span class="hljs-literal">true</span>, objectTypes: <span class="hljs-literal">true</span> },
 });

 <span class="hljs-built_in">console</span>.dir(res, { depth: <span class="hljs-literal">null</span> });
</code></pre>
<p> สังเกตุไหมว่า BALANCE_MANAGER_ID คือค่าที่เราได้จาก step 2 นั้นเองส่วน BALANCE_MANAGER_KEY คือ ชื่อที่เราสามรถอะไรก็ได้ ไว้สำหรับ อ้างอิงไปยัง BalanceManager object ไหนบน chain เเละในขั้นตอนนี้เราจะฝาก 0.5 SUI testnet token นะครับ ขั้นตอนนี้เมื่อสำเร็จเราจะได้ digest ของ transaction ซึ่งสามารถนำไปเปิดดูผ่าน <a target="_blank" href="https://suiscan.xyz/">https://suiscan.xyz/</a> ได้นะ แต่อย่าลืมเปลี่ยจาก Mainnet เป็น Testnet ด้วยนะ</p>
</li>
<li><p><strong>Read pool balance and Withdraw</strong></p>
<p> เมื่อเราทำการฝากเงินเข้าไปแล้ว เราก็จะมาทดสอบ อ่านข้อมูลกันสักหน่อย ว่าตัวเลขถูกต้องใหม และทำการถอนเงินออกมาด้วยเลย</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">import</span> <span class="hljs-string">"dotenv/config"</span>;
 <span class="hljs-keyword">import</span> { deepbook, <span class="hljs-keyword">type</span> BalanceManager } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/deepbook-v3"</span>;
 <span class="hljs-keyword">import</span> { SuiGrpcClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/grpc"</span>;
 <span class="hljs-keyword">import</span> { decodeSuiPrivateKey } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/cryptography"</span>;
 <span class="hljs-keyword">import</span> { Ed25519Keypair } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/keypairs/ed25519"</span>;
 <span class="hljs-keyword">import</span> { Transaction } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/transactions"</span>;

 <span class="hljs-keyword">const</span> env = (process.env.NETWORK ?? <span class="hljs-string">"testnet"</span>) <span class="hljs-keyword">as</span> <span class="hljs-string">"testnet"</span> | <span class="hljs-string">"mainnet"</span>;
 <span class="hljs-keyword">const</span> { scheme, secretKey } = decodeSuiPrivateKey(process.env.SUI_PRIVATE_KEY!);
 <span class="hljs-keyword">if</span> (scheme !== <span class="hljs-string">"ED25519"</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Unsupported scheme: <span class="hljs-subst">${scheme}</span>`</span>);
 <span class="hljs-keyword">const</span> keypair = Ed25519Keypair.fromSecretKey(secretKey);
 <span class="hljs-keyword">const</span> address = keypair.toSuiAddress();

 <span class="hljs-keyword">const</span> BALANCE_MANAGER_KEY = <span class="hljs-string">"BM1"</span>;
 <span class="hljs-keyword">const</span> BALANCE_MANAGER_ID = <span class="hljs-string">"0x09921a24e9fe239b194a109ed8b8812f86fefc3389f851c4d06f7e55bef79a9f"</span>;

 <span class="hljs-keyword">const</span> balanceManagers: { [k: <span class="hljs-built_in">string</span>]: BalanceManager } = { [BALANCE_MANAGER_KEY]: { address: BALANCE_MANAGER_ID } };

 <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> SuiGrpcClient({
   network: env,
   baseUrl: env === <span class="hljs-string">"mainnet"</span> ? <span class="hljs-string">"https://fullnode.mainnet.sui.io:443"</span> : <span class="hljs-string">"https://fullnode.testnet.sui.io:443"</span>,
 }).$extend(
   deepbook({
     address,
     balanceManagers,
   }),
 );

 <span class="hljs-keyword">const</span> suiBalance = <span class="hljs-keyword">await</span> client.deepbook.checkManagerBalance(BALANCE_MANAGER_KEY, <span class="hljs-string">"SUI"</span>);
 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"SUI in manager ="</span>, suiBalance);

 <span class="hljs-keyword">const</span> tx = <span class="hljs-keyword">new</span> Transaction();
 client.deepbook.balanceManager.withdrawAllFromManager(BALANCE_MANAGER_KEY, <span class="hljs-string">"SUI"</span>, address)(tx);

 <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> client.core.signAndExecuteTransaction({
   transaction: tx,
   signer: keypair,
   include: { effects: <span class="hljs-literal">true</span>, objectTypes: <span class="hljs-literal">true</span> },
 });

 <span class="hljs-built_in">console</span>.dir(res, { depth: <span class="hljs-literal">null</span> });
</code></pre>
<p> ซึ่งผลลัพธ์ที่ได้นั้น เราจะเห็นว่ามีอยู่ 0.5 SUI testnet token และเมื่อเราสั่งถอน เเละลองทดสอบรันอีกรอบจะเหลือ 0 SUI testnet token ซึ่งถ้ามี 0 token เราจะไม่สามารถวางคำสั่งเพื่อขาย SUI ได้นะ เพราะเราไม่มีเงินเพียงพอที่จะทำขายนั้นเอง ตามที่เคยอธิบายไปแล้วในบล็อคก่อนหน้า (Funding account = 0)</p>
</li>
</ol>
<hr />
<p>เป็นไงบ้างง่ายใช้ไหม เท่านี้เราก็พร้อมเเล้วที่เตรียมจะไปขั้นตอนถัดไปคือ การวางคำสั่งซื้อเเละขายนั้นเอง เอาไว้ต่อกันตอนหน้านะ ซึ่งน่าจะเป็นตอนสุดท้ายละ คิดว่าจะพยายามเขียนให้จบก่อนวัน ศุกร์ นี้</p>
<p>และก่อนจะจบบทความ ขอประชาสัมพันธ์หน่อย ON THE MOVE จะมี MOVE Workshop เร็วๆนี้ที่ CONTRIBUTIONDAO Office นะครับ ใครอยากลองมานั่งเขียนโค๊ดร่วมกันมากันได้น๊าา มีของกินตลอดทั้งวัน :P</p>
<ul>
<li><a target="_blank" href="https://luma.com/j743avm7">https://luma.com/j743avm7</a></li>
</ul>
<p>แล้วเจอกันนะครับ :)</p>
]]></content:encoded></item><item><title><![CDATA[DeepBook The Backbone of Sui DeFi Liquidity EP.1]]></title><description><![CDATA[DeFi เคยได้ยินคำนี้ไหมครับ DeFi ย่อมาจาก Decentralized Finance เป็นระบบการเงินเเบบไร้ศูนย์กลาง ถ้าเทียบกันให้เห็นภาพ ก็เหมือนกับการใช้บริการทางด้านการเงินกับทางธนาคาร ซึ่งเราจะต้องไป ยื่นเอกสารต่างๆ เพื่อขอเปิดบัญชี อาจจะไว้ ฝาก ออม หรือกู้ ใช่ไหมโดย...]]></description><link>https://onthemove.contributiondao.com/deepbook-ep1</link><guid isPermaLink="true">https://onthemove.contributiondao.com/deepbook-ep1</guid><category><![CDATA[deepbookv3]]></category><category><![CDATA[Sui]]></category><category><![CDATA[onthemoveth]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Sat, 31 Jan 2026 14:19:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769869338465/449fde78-038b-43e6-a0d8-d5fa57b0a67f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>DeFi เคยได้ยินคำนี้ไหมครับ DeFi ย่อมาจาก Decentralized Finance เป็นระบบการเงินเเบบไร้ศูนย์กลาง ถ้าเทียบกันให้เห็นภาพ ก็เหมือนกับการใช้บริการทางด้านการเงินกับทางธนาคาร ซึ่งเราจะต้องไป ยื่นเอกสารต่างๆ เพื่อขอเปิดบัญชี อาจจะไว้ ฝาก ออม หรือกู้ ใช่ไหมโดยทั้งหมดจะดูแลผ่านธนาคาร แต่ในโลกของ Web 3.0 นั้นแตกต่างไปจากนั้น เพราะไม่มีตัวกลางนั้นเอง ขอเเค่เรามีกระเป๋าเงิน เช่น Metamask, Rabbit wallet หรือ SUI Slush เราก็สามารถเข้าถึงบริการต่างๆได้ทันที โดยไร้ตัวกลาง โดยการทำงานทุกอย่างจะถูกกำหนดผ่าน Smart Contract นั้นเอง DeFi ตอนนี้ไม่ใช่เรื่องใหม่เเล้ว แต่เป็นผลิตภัณฑ์ทางการเงินที่ได้รับการยอมรับอย่างเป็นทางการ ในระดับสถาบันทางการเงินกันเลยทีเดียว</p>
<hr />
<p>เกริ่นไว้แค่นั้นพอเเหละ เดียวจะยาวที่พูดถึงเรื่องนี้ เพราะสัญญาเอาไว้ว่าจะเขียนบล็อคเกี่ยวกับ DeFi บน SUI network ซึ่งถ้าพูดถึงเรื่องนี้ ก็จะไม่พูดถึง DeepBook ไม่ได้ โดยที่</p>
<p><strong><em>DeepBook</em></strong> คือระบบซื้อขายแบบ Decentralized Central Limit Order Book (CLOB) บน SUI Network ที่ออกเเบบมาเพื่อการทำงานที่มีประสิทธิภาพสูง มีความเร็วนั้นเอง เพราะอาศัยการประมวลผลเเบบคู่ขนาดของ SUI ในการทำงาน ควบคู่ไปกับข้อดีในเรื่องค่าธรรมเนียมที่ต่ำ ซึ่งตัว DeepBook นั้นเป็นชุดพัฒนา ไม่ได้มี Interface ให้เราใช้งานนะ แต่จะมีพวกโครงสร้างพื้นฐานต่างๆให้เราสามารถนำไปใช้งานต่อยอดได้</p>
<p><em>CLOB คือระบบจับคู่คำสั่งซื้อขายที่ใช้กันอย่างแพร่หลายในตลาดซื้อขายแบบดั้งเดิมและศูนย์ซื้อขายคริปโตทั่วๆไป (เช่นตลาดหุ้นหรือ CEX) โดยผู้ใช้งานสามารถกำหนด ราคาที่ต้องการซื้อหรือขายล่วงหน้า ได้ผ่านคำสั่งที่เรียกว่า Limit Order ระบบจะนำคำสั่งซื้อ (Bid) และคำสั่งขาย (Ask) ใครเคยเทรดหุ้นมาบ้างก็พอจะเห็นภาพเน้อ เช่น เราต้องการซื้อหุ้น A ที่ราคา 20 บาท เราก็ส่งคำสั่งซื้อเข้าไปในระบบนั้นเอง</em></p>
<p>การทำงานเเละโครงสร้างพื้นฐานของ DeepBook</p>
<p>DeepBook ถูกออกเเบบให้มีการทำงาน 3 องค์ประกอบดังต่อไปนี้</p>
<ol>
<li><p><strong>Pool</strong></p>
<p> แทน ตลาดซื้อขายนั้นเอง เช่น SUI/USDC นั้นคือตลาดซื้อขายเเลกเปลี่ยน Token SUI กับ Stable token USDC โดยเป็น shared object ที่ผู้ใช้งานและแอปพลิเคชันต่าง ๆ สามารถเรียกใช้งานร่วมกันได้ ภายใน Pool จะรวมทุกสิ่งที่จำเป็นต่อการทำงานของ orderbook ไว้ในที่เดียว โดย Pool ถูกออกแบบให้แบ่งหน้าที่ภายในออกเป็นส่วนย่อย ได้แก่</p>
<ul>
<li><p>Book: ทำหน้าที่เก็บและจัดการ orderbook ทั้งฝั่ง bid (ราคาเสนอซื้อ) และ ask (ราคาเสนอขาย) รวมถึงการจับคู่คำสั่งซื้อขาย</p>
</li>
<li><p>State: จัดการสถานะของตลาด เช่น ค่าธรรมเนียม, ประวัติการซื้อขาย และกฎต่าง ๆ ของพูล</p>
</li>
<li><p>Vault: ดูแลการ settle และการเคลื่อนย้ายสินทรัพย์จริงหลังจากคำสั่งถูกจับคู่สำเร็จแล้ว</p>
</li>
</ul>
</li>
</ol>
<p>    เมื่อมีการวางคำสั่งซื้อขายหรือทำการแลกเปลี่ยน ระบบจะประมวลผลทั้งหมดภายใน Pool แบบ atomic transaction เดียว ทำให้มั่นใจได้ว่าการจับคู่คำสั่งและการเคลื่อนย้ายสินทรัพย์จะสอดคล้องกันเสมอ</p>
<ol start="2">
<li><p><strong>PoolRegistry</strong></p>
<p> ทำหน้าที่เป็น registry กลางของระบบ DeepBookV3 โดยมีบทบาทสำคัญคือ</p>
<ul>
<li><p>เก็บรายการ Pool ทั้งหมดที่มีอยู่ในระบบ</p>
</li>
<li><p>เป็นจุดอ้างอิงสำหรับแอปพลิเคชันหรือ SDK ในการค้นหา pool ที่ต้องการใช้งาน</p>
</li>
<li><p>ควบคุมการสร้าง Pool ใหม่ เพื่อให้โครงสร้างของตลาดเป็นไปตามกฎของโปรโตคอล</p>
</li>
</ul>
</li>
</ol>
<p>    ด้วยการมี PoolRegistry ทำให้ DeepBookV3 สามารถขยายไปสู่หลายตลาดได้อย่างเป็นระบบ และลดความซับซ้อนในการ integrate สำหรับ dApp, wallet หรือ DEX ต่าง ๆ ที่ต้องการเข้าถึงสภาพคล่องจากหลายคู่เทรดได้</p>
<ol start="3">
<li><p><strong>BalanceManager</strong></p>
<p> เป็นองค์ประกอบสำคัญที่เชื่อมระหว่างผู้ใช้งานกับ Pool โดยทำหน้าที่เป็นบัญชีสำหรับการเทรดบน DeepBook แยกออกจากกระเป๋าปกติของผู้ใช้งาน ซึ่งต้องบอกก่อนว่าใน การเทรดทุกครั้งต้องผ่าน BalanceManager ซึ่งเป็น shared object ที่เก็บยอดเงินและใช้เป็นแหล่งเงินต้นในการวาง orders หรือ swap ในแต่ละ pool (ยกเว้นบางกรณีของ swap) และเมื่อคำสั่งถูก match แล้วระบบจะโอนเงินเข้าออก BalanceManager แทนที่จะเทรดโดยตรงกับกระเป๋าของผู้ใช้งาน</p>
<p> บทบาทหลักของ BalanceManager ที่สำคัญๆ คือ</p>
<ul>
<li><p>เก็บยอดคงเหลือของสินทรัพย์ที่ผู้ใช้งานนำมาใช้สำหรับการเทรด</p>
</li>
<li><p>ใช้เป็นแหล่งเงินในการวางคำสั่งซื้อขาย (limit order / market order)</p>
</li>
<li><p>รับผลลัพธ์จากการจับคู่คำสั่ง เช่น เหรียญที่ได้จากการเทรด หรือยอดที่เหลือหลังจาก Partial fill</p>
</li>
</ul>
</li>
</ol>
<p>    การออกแบบให้ผู้ใช้งานต้องฝากสินทรัพย์เข้า BalanceManager ก่อนเทรด ช่วยให้ระบบสามารถจัดการคำสั่งซื้อขายได้อย่างมีประสิทธิภาพ ลดความซับซ้อนของ state และรองรับการทำงานแบบขนานของ Sui ได้ดียิ่งขึ้น</p>
<p>มาดูตัวอย่างการทำงานกัน</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769867039967/606bd6aa-58ea-4b1e-8ec2-6e6bd171204b.png" alt class="image--center mx-auto" /></p>
<p>อิงจากรูปนี้นะ</p>
<ol>
<li><p>เราทำการฝากเงินเข้าไปที่ Balance Manager เพื่อทำการส่งคำสั่งซื้อหรือขายนั้นเอง เช่น เราบอกว่าจะขาย SUI token ก็จะเป็นการฝาก SUI เข้าไปที่ Balance Manager เป็นขั้นตอนแรก ถ้าเรายกเลิกคำสั่งซื้อเงินก็จะถูกถอนกลับมาที่กระเป๋าของเราทันที</p>
</li>
<li><p>Swap Exact/External Contract ตรงส่วนนี้จะทำให้ DEX, Router, Aggregator ต่างๆ สามารถทำการเเลกเปลี่ยนเหรียญได้ ถ้าจะให้เขาใจง่ายๆก็เหมือน Market order ละมั่ง อารมณ์แบบอยากซื้อราคาที่ตลาดตอนนี้เลย ถ้าสมมุติว่า SUI มีมูลค่า 10 บาทต่อ tokens เเล้วเรามีอยู่ 100 บาท เราไม่อยากรอให้ราคาลงมาที่ 9 บาท เราก็จะทำการ Swap Exact โดยเเค่บอกว่า เรามีเงินอยู่ 100 บาทนะ เอาไปแลก SUI ให้หน่อย ซึ่งเราอาจจะได้ SUI กลับมาไม่ถึง 10 tokens ก็เป็นได้ ไม่งงใช่ไหม</p>
</li>
<li><p>DeepBook V3 Contract ส่วนนี้จะเป็นหัวใจหลัก หน้าที่มันก็จะมีจำพวก</p>
<ul>
<li><p>รับคำสั่งจากมาจาก ผู้ใช้งาน หรือ DEX ต่างๆ</p>
</li>
<li><p>จัดการ logic ของ orderbook / matching / settlement</p>
</li>
<li><p>ประสานงานกับ Pool และ Balance Manager</p>
</li>
</ul>
</li>
</ol>
<p>ทั้งหมดคือข้อมูลคร่าวๆนะ ตามไปอ่านต่อกันได้ที่ <a target="_blank" href="https://docs.sui.io/standards/deepbook">https://docs.sui.io/standards/deepbook</a> ซึ่ง บทความถัดไปเราจะไม่เกริ่นอะไรกันละ เราจะมาเริ่มลงมือเขียนโค๊ดกัน เริ่มกันตั้งแต่ สร้าง Balance Manager การวางคำสั่งซื้อ คำสั่งขาย การอ่านค่า order book เป็นต้น ซึ่งไม่รู้จะจบในกี่ตอน ยังไงก็ฝากติดตามกันด้วยน๊า อ่อลืมบอกไปตอนนี้ DeepBook มีการเก็บคะเเนนการใช้งานด้วยนะ ซึ่งในโลกของ Web 3.0 คะแนน ย่อม หมายถึง เงินฟรี ที่อาจจะเเยกในอนาคน :P</p>
]]></content:encoded></item><item><title><![CDATA[zkLogin ท่าไม้ตายสำหรับ Crypto Adoption]]></title><description><![CDATA[สวัสดีครับ ปี 2026 แล้ว มีใครยังเจอกับปัญหาจดจำ Seed phase หรือ Private key อยู่บ้างไหมครับ นี้คือปัญหาในด้าน UX ที่ทำให้ผู้ใช้งานผลิตภัณฑ์ต่างๆของ Web 3.0 รู้สึกเข้าถึงยาก เพราะแค่เริ่มก็ต้องเจอกับอุปสรรคในการใช้งานแล้ว ในช่วงปีที่ผ่านมา เราจึงเห็นว...]]></description><link>https://onthemove.contributiondao.com/sui-zklogin</link><guid isPermaLink="true">https://onthemove.contributiondao.com/sui-zklogin</guid><category><![CDATA[Sui]]></category><category><![CDATA[onthemoveth]]></category><category><![CDATA[zklogin]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Thu, 22 Jan 2026 08:10:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769069327208/c4a831d7-9632-426b-ad7b-068200463d56.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>สวัสดีครับ ปี 2026 แล้ว มีใครยังเจอกับปัญหาจดจำ Seed phase หรือ Private key อยู่บ้างไหมครับ นี้คือปัญหาในด้าน UX ที่ทำให้ผู้ใช้งานผลิตภัณฑ์ต่างๆของ Web 3.0 รู้สึกเข้าถึงยาก เพราะแค่เริ่มก็ต้องเจอกับอุปสรรคในการใช้งานแล้ว ในช่วงปีที่ผ่านมา เราจึงเห็นว่า Platform ต่างๆเริ่มมีวิธีการเเก้ไขปัญหาเหล่านี้มากขึ้น ผู้ใช้งานไม่จำเป็นจะต้องมีกระเป๋าของตัวเอง แต่สามารถเชื่อมต่อบัญชีเช่น X, Google เพื่อเข้าใช้งานได้ทันที ซึ่งเบื้องหลังการทำงานก็จะมีผู้ให้บริการคอยดูแลกระเป๋าเงินให้เรานั้นเอง เช่น <a target="_blank" href="https://www.privy.io/">Priv</a>y</p>
<p><strong>ZKLogin บน Sui</strong> คือกลไกการล็อกอินที่ผสาน Zero-Knowledge Proof เข้ากับ OAuth (เช่น Google หรือ X) เพื่อให้ผู้ใช้เข้าใช้งาน Web 3.0 ได้โดยไม่ต้องสร้างหรือเก็บ Private key เอง ระบบจะพิสูจน์ตัวตนว่า <strong>ผู้ใช้ล็อกอินถูกต้อง</strong> โดยไม่เปิดเผยข้อมูลส่วนบุคคลบนบล็อกเชน และสร้างบัญชีที่ผูกกับ session ผลลัพธ์คือประสบการณ์ใช้งานที่ใกล้เคียง Web 2.0 แต่ยังคงคุณสมบัติด้านความเป็นส่วนตัวและความปลอดภัยแบบ Web 3.0 ลดปัญหาสำหรับผู้ใช้งานมือใหม่ และช่วยให้นักพัฒนาสร้าง dApp ที่ onboard คนได้ง่ายขึ้นเป็นอย่างมาก</p>
<p>เรามาดูวิธีการตัวอย่างการ Implement ZK Login กันดีกว่า</p>
<hr />
<p>สิ่งต้องเตรียม</p>
<ul>
<li><p>Node.js (เวอร์ชัน 18 ขึ้นไปแนะนำ)</p>
</li>
<li><p>Sui TypeScript SDK: <code>@mysten/sui</code></p>
</li>
<li><p>Google Cloud Project: เพื่อเอา Client ID (เพราะเราจะจำลองการ Login ด้วย Google account)</p>
</li>
</ul>
<p>มาเริ่มกัน</p>
<ol>
<li><p>ติดตั้ง SDK</p>
<pre><code class="lang-typescript"> npm install <span class="hljs-meta">@mysten</span>/sui jwt-decode
</code></pre>
</li>
<li><p>เตรียม Google OAuth (Client ID)</p>
<p> เริ่มจากเราต้องไปที่ <a target="_blank" href="https://console.cloud.google.com/">Google Cloud Console</a> แล้วทำตามขั้นตอนดังนี้</p>
<ul>
<li><p>เริ่มจากสร้าง Project ใหม่ ตรงนี้จะต้องกรอกข้อมูลพื้นฐาน เช่น ข้อมูลที่อยู่ออกบิล เเละข้อมูลบัตรเครติด ซึ่งเราไม่ต้องกังวลไปนะ เพราะเราจะใช้งานเเบบฟรี แถมได้เครติดฟรีด้วย</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769007555679/79871a04-bfa5-4c2e-b6ef-47a004f382da.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>ไปที่ APIs &amp; Services &gt; Credentials (<a target="_blank" href="https://console.cloud.google.com/apis/credentials">https://console.cloud.google.com/apis/credentials</a>)</p>
</li>
<li><p>สร้าง OAuth client ID กรอกข้อมูลต่างตามตัวอย่างรูปข้างล่าง และตั้งค่า Authorized redirect URIs ให้ตรงกับหน้าเว็บของเรา (เช่น <a target="_blank" href="http://localhost:5173">http://localhost:5173</a>)</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769007843702/ce91f4ee-cbda-4ef7-a3e4-6b17a5f48854.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Copy Client ID เก็บไว้นะ เราต้องนำไปใช้งานต่อ</p>
</li>
</ul>
</li>
<li><p>เริ่มเขียนโค๊ดกัน</p>
<p> ซึ่งการทำ ZK Login จะมี Flow หลักๆ ดังนี้</p>
<ul>
<li><p>สร้างกุญแจชั่วคราว (Ephemeral Key)</p>
</li>
<li><p>สร้าง Nonce และส่ง User ไปล็อกอิน Google</p>
</li>
<li><p>รับ JWT Token จาก Google</p>
</li>
<li><p>สร้าง ZK Proof และส่ง Transaction</p>
</li>
</ul>
</li>
</ol>
<ul>
<li><p>เริ่มจาก สร้าง Ephemeral Key และ URL สำหรับ Login</p>
<p>  อันดับแรก เราต้องสร้างกุญแจชั่วคราวก่อน เพื่อใช้เซ็นธุรกรรมแทน User ใน Session ที่ผู้ใช้งานได้เข้าสู่ระบบ</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">import</span> { Ed25519Keypair } <span class="hljs-keyword">from</span> <span class="hljs-string">'@mysten/sui/keypairs/ed25519'</span>;
  <span class="hljs-keyword">import</span> { generateNonce, generateRandomness } <span class="hljs-keyword">from</span> <span class="hljs-string">'@mysten/sui/zklogin'</span>;

  <span class="hljs-comment">// 1. สร้างกุญแจชั่วคราว (Ephemeral Key)</span>
  <span class="hljs-keyword">const</span> ephemeralKeyPair = <span class="hljs-keyword">new</span> Ed25519Keypair();

  <span class="hljs-comment">// 2. กำหนดค่าพื้นฐาน</span>
  <span class="hljs-keyword">const</span> maxEpoch = <span class="hljs-number">100</span>; <span class="hljs-comment">// กำหนดอายุการใช้งานของ Key (เช็ค epoch ปัจจุบันจาก RPC ได้)</span>
  <span class="hljs-keyword">const</span> randomness = generateRandomness(); <span class="hljs-comment">// ค่าสุ่มเพื่อความปลอดภัย</span>

  <span class="hljs-comment">// 3. สร้าง Nonce</span>
  <span class="hljs-comment">// Nonce นี้จะผูก Public Key ชั่วคราวของเราเข้ากับ Login Session ของ Google</span>
  <span class="hljs-keyword">const</span> nonce = generateNonce(
      ephemeralKeyPair.getPublicKey(), 
      maxEpoch, 
      randomness
  );

  <span class="hljs-comment">// 4. สร้าง URL เพื่อส่ง User ไปหน้า Login Google</span>
  <span class="hljs-keyword">const</span> REDIRECT_URI = <span class="hljs-string">'http://localhost:5173'</span>; <span class="hljs-comment">// URL หน้าเว็บของคุณ</span>
  <span class="hljs-keyword">const</span> CLIENT_ID = <span class="hljs-string">'YOUR_GOOGLE_CLIENT_ID'</span>; <span class="hljs-comment">// ใส่ Client ID ที่ได้จาก Google</span>

  <span class="hljs-keyword">const</span> params = <span class="hljs-keyword">new</span> URLSearchParams({
      client_id: CLIENT_ID,
      response_type: <span class="hljs-string">'id_token'</span>,
      redirect_uri: REDIRECT_URI,
      scope: <span class="hljs-string">'openid email'</span>, <span class="hljs-comment">// ขอสิทธิ์ดู email</span>
      nonce: nonce, <span class="hljs-comment">// สำคัญมาก! ต้องส่ง nonce ไปด้วย</span>
  });

  <span class="hljs-keyword">const</span> loginURL = <span class="hljs-string">`https://accounts.google.com/o/oauth2/v2/auth?<span class="hljs-subst">${params}</span>`</span>;

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Login Link:'</span>, loginURL);
  <span class="hljs-comment">// ในเว็บจริง: window.location.href = loginURL;</span>
</code></pre>
</li>
<li><p>รับ JWT และสร้าง Sui Address (หลัง User ล็อกอินเสร็จ)</p>
<p>  จากขั้นตอนแรกเราจะได้ Login Link ให้นำ URL ดังกล่าวไปเปิดผ่าน browser นะ และเมื่อผู้ใช้งานล็อกอินสำเร็จ Google จะ redirect กลับมาพร้อม id_token (JWT) ใน URL นั้นเอง และเมื่อเราได้ SUI address มาเเล้วอย่าลืมไปขอ Devent token นะ</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">import</span> { jwtToAddress } <span class="hljs-keyword">from</span> <span class="hljs-string">'@mysten/sui/zklogin'</span>;
  <span class="hljs-keyword">import</span> { jwtDecode } <span class="hljs-keyword">from</span> <span class="hljs-string">'jwt-decode'</span>;

  <span class="hljs-comment">// สมมติว่าดึง id_token มาจาก URL แล้ว</span>
  <span class="hljs-keyword">const</span> idToken = <span class="hljs-string">'...JWT_TOKEN_FROM_GOOGLE_URL...'</span>; 

  <span class="hljs-comment">// ถอดรหัส JWT เพื่อดูข้อมูล (ไม่จำเป็นต้องใช้ private key)</span>
  <span class="hljs-keyword">const</span> decodedJwt = jwtDecode(idToken);

  <span class="hljs-comment">// 5. กำหนด Salt (User Salt)</span>
  <span class="hljs-comment">// *สำคัญ* Salt ต้องเป็นความลับและ "เหมือนเดิมเสมอ" สำหรับ User คนเดิม</span>
  <span class="hljs-comment">// ในโปรดักชั่น ควรมี Salt Service แยกต่างหากเพื่อ Map User ID -&gt; Salt</span>
  <span class="hljs-keyword">const</span> userSalt = <span class="hljs-string">'12345678901234567890'</span>; <span class="hljs-comment">// ตัวอย่าง (จริงๆ ต้องเป็น BigInt string)</span>

  <span class="hljs-comment">// 6. คำนวณหา Sui Address ของ User คนนี้</span>
  <span class="hljs-keyword">const</span> zkLoginAddress = jwtToAddress(idToken, userSalt);

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Sui Address ของ User คือ:'</span>, zkLoginAddress);
</code></pre>
</li>
<li><p>สร้าง Transaction และส่งขึ้นธุรกรรมขึ้น SUI network</p>
<p>  ขั้นตอนนี้ต้องใช้ Proving Service (บริการสร้าง Zero-Knowledge Proof) ซึ่งปกติ Mysten Labs มีให้ใช้ฟรีใน Devnet หรือเราจะรันเองใน Mainnet ก็ได้</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">import</span> { SuiClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@mysten/sui/client'</span>;
  <span class="hljs-keyword">import</span> { Transaction } <span class="hljs-keyword">from</span> <span class="hljs-string">'@mysten/sui/transactions'</span>;
  <span class="hljs-keyword">import</span> { genAddressSeed, getZkLoginSignature } <span class="hljs-keyword">from</span> <span class="hljs-string">'@mysten/sui/zklogin'</span>;

  <span class="hljs-comment">// เชื่อมต่อ SUI Network</span>
  <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> SuiClient({ url: <span class="hljs-string">'https://fullnode.devnet.sui.io'</span> });

  <span class="hljs-comment">// --- (ส่วนนี้คือการขอ ZK Proof จาก Proving Service) ---</span>
  <span class="hljs-comment">// เราต้องส่ง JWT, Salt, Ephemeral Public Key, ฯลฯ ไปที่ Prover API</span>
  <span class="hljs-comment">// Devnet =&gt; https://prover-dev.mystenlabs.com/v1</span>
  <span class="hljs-comment">// สมมติว่าได้ proof กลับมาแล้วเก็บในตัวแปร `zkProof`</span>
  <span class="hljs-comment">// const zkProof = await fetchProverService(...); </span>

  <span class="hljs-comment">// 7. สร้าง Transaction (ตัวอย่างส่งเหรียญ SUI)</span>
  <span class="hljs-keyword">const</span> tx = <span class="hljs-keyword">new</span> Transaction();
  tx.setSender(zkLoginAddress);
  <span class="hljs-keyword">const</span> [coin] = tx.splitCoins(tx.gas, [<span class="hljs-number">1000</span>]); <span class="hljs-comment">// ส่ง 1000 MIST</span>
  tx.transferObjects([coin], <span class="hljs-string">'0xRECIPIENT_ADDRESS'</span>);

  <span class="hljs-comment">// 8. เซ็น Transaction ด้วย Ephemeral Key (กุญแจชั่วคราว)</span>
  <span class="hljs-keyword">const</span> { bytes, signature: userSignature } = <span class="hljs-keyword">await</span> tx.sign({
      client,
      signer: ephemeralKeyPair, <span class="hljs-comment">// ใช้ Key ที่สร้างในขั้นตอนที่ 1</span>
  });

  <span class="hljs-comment">// 9. สร้าง Signature (รวม ZK Proof + Signature)</span>
  <span class="hljs-keyword">const</span> addressSeed = genAddressSeed(
      BigInt(userSalt), 
      <span class="hljs-string">'sub'</span>, 
      decodedJwt.sub, 
      decodedJwt.aud
  ).toString();

  <span class="hljs-keyword">const</span> zkLoginSignature = getZkLoginSignature({
      inputs: {
          ...zkProof, <span class="hljs-comment">// Proof ที่ได้จาก Prover</span>
          addressSeed,
      },
      maxEpoch,
      userSignature, <span class="hljs-comment">// Signature จาก Ephemeral Key</span>
  });

  <span class="hljs-comment">// 10. ส่ง Transaction ขึ้น SUI network</span>
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> client.executeTransactionBlock({
      transactionBlock: bytes,
      signature: zkLoginSignature,
  });

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Transaction Success:'</span>, result.digest);
</code></pre>
</li>
</ul>
<p>เรียบร้อย พอจะเข้าในการทำงานในแต่ละขั้นตอนใช่ไหม ซึ่งเราสามารถนำความเข้าตรงนี้ไปพัฒนาต่อเป็น Web application (ดูตัวอย่างได้ที่นี้ <a target="_blank" href="https://github.com/jovicheng/sui-zklogin-demo">https://github.com/jovicheng/sui-zklogin-demo</a>) ให้ผู้ใช้งานสามารถสร้างกระเป๋าด้วยบัญชี Google ของเขาได้ทันที หรือ สามารถลองเล่นทีละขั้นตอนได้ที่นี้นะ <a target="_blank" href="https://sui-zklogin.vercel.app/">https://sui-zklogin.vercel.app/</a> ซึ่งจะมีตัวอย่างให้เห็นอย่างชัดเจนว่าแต่ละขั้นตอนมีการเรียกใช้งานโค๊ดส่วนไหน ลองเอาไปใช้กันดูน๊า เเล้วลูกค้าจะรักในผลิตภัณฑ์เรามากยิ่งขึ้น</p>
]]></content:encoded></item><item><title><![CDATA[สรุปภาพรวมของ Sui เทคโนโลยีในปี 2025]]></title><description><![CDATA[สวัสดีครับ สวัสดีปีใหม่ ขอโทษที่ห่างหายไปนานเป็นเดือน ชีวิตยังวนเวียนกับการปั่นงาน จนไม่ค่อยมีเวลาสักเท่าไหร่ ก่อนที่เราจะลุยกันต่อในปี 2026 เรามาสรุปภาพรวมไวๆของ SUI ดีกว่า ว่าในตลอดปี 2025 ที่ผ่านมา SUI Technology มีอะไรที่สำคัญเกิดขึ้นมาบ้าง

Myst...]]></description><link>https://onthemove.contributiondao.com/sui-technology-summarize-2025</link><guid isPermaLink="true">https://onthemove.contributiondao.com/sui-technology-summarize-2025</guid><category><![CDATA[Sui]]></category><category><![CDATA[onthemoveth]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Sun, 18 Jan 2026 08:35:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768724280917/d609e5eb-6acb-4694-a2f4-1e7d64a5384b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>สวัสดีครับ สวัสดีปีใหม่ ขอโทษที่ห่างหายไปนานเป็นเดือน ชีวิตยังวนเวียนกับการปั่นงาน จนไม่ค่อยมีเวลาสักเท่าไหร่ ก่อนที่เราจะลุยกันต่อในปี 2026 เรามาสรุปภาพรวมไวๆของ SUI ดีกว่า ว่าในตลอดปี 2025 ที่ผ่านมา SUI Technology มีอะไรที่สำคัญเกิดขึ้นมาบ้าง</p>
<hr />
<p><strong>Mysticeti v2</strong></p>
<p>เริ่มจากตัวหลักก่อนเลย Mysticeti v2 ซึ่งเป็น Consensus หลักของ SUI โดยทีมได้มีการประกาศอย่างเป็นทางการช่วงเดือน Nov ที่ผ่านมา ตัว v2 แน่นอนได้ต่อยอดจาก v1 ที่เปิดตัวไปเมื่อกลางปี 2024 นู้น ใจความหลักๆเลยคือการปรับปรุงในเรื่องความเร็ว เเละทำให้มีความยืดหยุ่นมากขึ้น โดยมีการปรับปรุงในส่วนของการลดทอนขั้นตอนที่ไม่จำเป็นของ การส่งเเละการยืนยันธุรกรรมในระบบ รวมถึงทำให้มีการใช้ทรัพยากรน้อยลง ส่วนถ้าอยากรู้ว่า Mysticeti v2 ทำงานยังไงเดียวทีม ContributionDAO น่าจะมี Live บน X เเหละ เพื่ออธิบายกันอีกที แต่ก่อนจะเรียนรู้ตรงนี้ อย่างลืมไปดู Live เก่าๆก่อนนะ</p>
<ul>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=ktE6SVQPEnY&amp;t=66s">https://www.youtube.com/watch?v=ktE6SVQPEnY</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=klhO9NbaFwI&amp;t=543s">https://www.youtube.com/watch?v=klhO9NbaFwI</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=A18qJrgt7rI&amp;t=3s">https://www.youtube.com/watch?v=A18qJrgt7rI</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=39I8v4tGvUI">https://www.youtube.com/watch?v=39I8v4tGvUI</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=MTXKsWg-E8Y">https://www.youtube.com/watch?v=MTXKsWg-E8Y</a></p>
</li>
</ul>
<p>5 vdo จุกๆ กันไป</p>
<p>…</p>
<p><strong>Walrus - Programmable Storage</strong></p>
<p>ในเดือนมีนาคม 2025 ที่ผ่านมา ทีม Mysten Labs ได้เปิดตัว <strong>Walrus</strong> บน Mainnet ซึ่งถือเป็นการเปลี่ยนแปลงเชิงโครงสร้างพื้นฐานที่สำคัญที่สุดสำหรับการจัดการข้อมูลบน Sui ก่อนหน้านี้ การจัดเก็บไฟล์ขนาดใหญ่ (Blobs) เช่น รูปภาพ วิดีโอ หรือโมเดล AI บน Layer 1 โดยตรงนั้นเป็นเรื่องที่เป็นไปไม่ได้ ซึ่ง Walrus ได้เข้ามาแก้ปัญหานี้ด้วยการสร้างเลเยอร์จัดเก็บข้อมูลแบบกระจายศูนย์ที่เชื่อมต่อกับ Sui ecosystem</p>
<p>…</p>
<p><strong>Seal - Data encryption and onchain access control</strong></p>
<p>Privacy ไม่เคย เเละไม่เคยหายไปใหน ถ้าใครอยู่ใน Web 3.0 ecosystem เราจะเห็นเทรนของ Privacy เวียนว่ายตายเกิดกลับมาเป็นระยะ Seal เป็นก็เป็น Privacy player นึงที่สำคัญบน SUI ecosystem ซึ่งได้เปิดตัวบน Mainnet ไปตั้งแต่ช่วงกันยายนปีที่แล้ว โดย use case หลักที่จะเห็นได้ชัดเจนเลยคือการนำ Access Control ของ Seal ไปทำงานควบคู่กับ Walrus ซึ่งทางเราก็เคยเขียนตัวอย่างไว้ละ ลองอ่านย้อนหลังตาม blogs เหล่านี้ดูนะ</p>
<ul>
<li><p><a target="_blank" href="https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message">https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message</a></p>
</li>
<li><p><a target="_blank" href="https://onthemove.contributiondao.com/ep-4-seal-on-chain-access-control">https://onthemove.contributiondao.com/ep-4-seal-on-chain-access-control</a></p>
</li>
<li><p><a target="_blank" href="https://onthemove.contributiondao.com/ep-5-walrus-seal-send-receive-data">https://onthemove.contributiondao.com/ep-5-walrus-seal-send-receive-data</a></p>
</li>
</ul>
<p>…</p>
<p><strong>The Agentic Web</strong></p>
<p>ก็…AI นั้นเเหละ มีหลายตัวที่เปิดตัวไปแล้ว มีตัวนึงที่เรากำลังศึกษาคือ Nautilus โดยเปิดตัวบน Mainnet ในช่วงกลางปี 2025 ถือเป็นจิ๊กซอว์ชิ้นสำคัญของ Sui Stack ที่เข้ามาจัดการข้อมูลระหว่างโลก On-chain และ Off-chain โดยทำหน้าที่เป็นเลเยอร์สำหรับการ Indexing ข้อมูล และการประมวลผลนอกเชน (Off-chain Computation) เทคโนโลยีนี้ช่วยให้นักพัฒนาสามารถสร้างแอปพลิเคชันที่มีความซับซ้อนสูง เช่น AI Agents โดยไม่ต้องแบกรับต้นทุนในการเก็บข้อมูลหรือประมวลผลทุกอย่างบนบล็อกเชนโดยตรง ซึ่งเป็นจุดเด่นสำคัญของ Nautilus เขาละ ทำให้ เชื่อมต่อข้อมูลจากภายนอกหรือผลลัพธ์การคำนวณที่ซับซ้อนกลับเข้ามาสู่ Smart Contract ได้อย่างถูกต้อง ไว้เดียวเขียน Blog เเยกดีกว่าจะได้เห็นตัวอย่างกันด้วย</p>
<p>กับอีกส่วนนึงที่ทุกคนน่าจะเคยเห็นเเล้วมั่ง…เคยเเหละคือ Agentic Payments Protocol (AP2) ของพี่ใหญ่ Google ของเรานั้นเอง ซึ่ง SUI ก็เป็นหนึ่งในพันธมิตรกับเขาด้วย ก็ต้องรอดูต่อไปว่าจะมีอะไรน่าสนใจมาไหม</p>
<p>…</p>
<p><strong>Move Registry (MVR)</strong></p>
<p>ในช่วงหน้าร้อนของไทย ก็คือเมษาปีที่แล้ว ทีม Mysten Labs ได้เปิดตัว Move Registry (MVR) ซึ่งถือเป็นเครื่องมือใหม่ใน ecosystem ที่ช่วยยกระดับประสบการณ์นักพัฒนาบน Sui เป็นอย่างมาก MVR คือระบบ จัดการแพ็กเกจ (package management) แบบ on-chain สำหรับโค้ด Move ของ Sui ที่ทำหน้าที่คล้ายกับ npm (ของ JavaScript) หรือ <a target="_blank" href="http://crates.io">crates.io</a> (ของ Rust) ในโลกของบล็อกเชนนั้นเอง โดยนักพัฒนาสามารถ เผยแพร่แพ็กเกจ Move ขึ้นไปยัง MVR ได้ ใครสนใจลองไปส่องกันดูที่นี้นะ <a target="_blank" href="https://www.moveregistry.com/">https://www.moveregistry.com/</a></p>
<p>…</p>
<p><strong>Move lang/SDK</strong></p>
<p>ที่เข้าตาเลยก็คือ SUI Stack Messaging SDK ซึ่งเป็นเครื่องมือใหม่ที่เปิดตัวในปี 2025 เพื่อทำให้ระบบส่งข้อความแบบกระจายศูนย์กลายเป็นส่วนหนึ่งของการพัฒนาแอปบน Sui ได้โดยตรง แทนที่จะต้องพึ่งโซลูชันภายนอกหรือ API นักพัฒนาสามารถสร้าง การสื่อสารที่เข้ารหัสลับตั้งแต่ต้นทางถึงปลายทาง (end-to-end encrypted) ที่เชื่อมกับ wallet identity และสามารถจัดเก็บข้อความและไฟล์แนบได้อย่างปลอดภัยบนเครือข่าย Sui พร้อมการเข้าถึงที่กำหนดเองได้ผ่านโมดูลต่าง ๆ ของ stack เช่น Sui blockchain, Walrus decentralized storage, และ Seal access control ซึ่งจาก SDK นี้นักพัฒนาสามารถสร้างฟีเจอร์ที่หลากหลายได้ตั้งแต่ข้อความแบบหนึ่ง-ต่อ-หนึ่ง, ห้องแชทกลุ่ม, ไปจนถึงการสื่อสารที่ถูกทริกเกอร์โดยกิจกรรมบนเชน เช่น การ mint NFT หรือการลงคะแนนโหวต</p>
<hr />
<p>น่าจะสรุปภาพรวมประมาณนี้ ถ้าใครมีอะไรอยากจะเเชร์ จัดมาได้เลยนะ คิดว่าปีนี้ อยากจะมี workshop แหละ อยู่หลังคีบอร์ดมานานเเล้ว อยากเจอหน้าทุกคนบ้าง เดียวทีมจะมีประกาศเร็วๆนี้นะ ถ้าใครสนใจมาร่วมกันได้เลย หรือถ้าใครอยากให้สอนหัวข้ออะไรใน workshop บอกได้น๊าา</p>
]]></content:encoded></item><item><title><![CDATA[EP 5:  การรับส่งข้อมูลเข้ารหัสด้วย Walrus + Seal]]></title><description><![CDATA[สวัสดีครับ เดินทางมาถึง EP สุดท้ายแล้ว สำหรับบทความเกี่ยวกับการใช้ Walrus และ Seal ในการสร้างเนื้อหาที่มีความ Privacy ก่อนอื่นเราย้อนไปดูกันสักนิดดีกว่า เราทำอะไรมาแล้วบ้าง

https://onthemove.contribu]]></description><link>https://onthemove.contributiondao.com/ep-5-walrus-seal-send-receive-data</link><guid isPermaLink="true">https://onthemove.contributiondao.com/ep-5-walrus-seal-send-receive-data</guid><category><![CDATA[Sui]]></category><category><![CDATA[Seal]]></category><category><![CDATA[walrus]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Sat, 06 Dec 2025 15:14:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765033676679/a8269cca-269c-4852-ac96-133923fbe008.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>สวัสดีครับ เดินทางมาถึง EP สุดท้ายแล้ว สำหรับบทความเกี่ยวกับการใช้ Walrus และ Seal ในการสร้างเนื้อหาที่มีความ Privacy ก่อนอื่นเราย้อนไปดูกันสักนิดดีกว่า เราทำอะไรมาแล้วบ้าง</p>
<ul>
<li><p><a href="https://onthemove.contributiondao.com/ep-1-walrus-and-seal">https://onthemove.contributiondao.com/ep-1-walrus-and-seal</a> เริ่มต้นด้วยตัวอย่างโค๊ด ในการส่งข้อมูลไปเก็บที่ Walrus</p>
</li>
<li><p><a href="https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage">https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage</a> ทำความรู้จักการเขียน Programmable Storage</p>
</li>
<li><p><a href="https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message">https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message</a> รู้จัก Seal และตัวอย่างโค๊ด ในการเข้ารหัสข้อมูล</p>
</li>
<li><p><a href="https://onthemove.contributiondao.com/ep-4-seal-on-chain-access-control">https://onthemove.contributiondao.com/ep-4-seal-on-chain-access-control</a> ตัวอย่าง Move Smart Contract ในการจัดการ Access Control</p>
</li>
</ul>
<p>ลองไปอ่านดูย้อนหลังกันก่อนน๊ะ เพราะใน EP เราจะนำทุกอย่างมาประกอบร่างกันแล้วครับ</p>
<hr />
<p>จากตัวอย่างโค๊ดล่าสุดนั้น เราเขียนละติดปัญหาเกี่ยวกับฟังก์ชั่นของ Walrus พอสมควร เเละพบว่าจริงๆเเล้ว เราสามารถเรียกใช้งาน API ผ่าน Provider endpoint เพื่อ ทำการรับเเละส่งข้อมูลไปยัง Walrus network ได้ เลยขอเปลี่ยนวิธีการรับส่งข้อมูลกันหน่อยนึงนะ</p>
<p>มาเริ่มกันเลย นี้คือตัวอย่างโค๊ดทั้งหมดที่นำมาประกอบร่างและ มีการทำให้โค๊ดอ่านง่ายขึ้นละ</p>
<ol>
<li><p>ทำการเเก้ไข <code>.env</code> กันก่อน</p>
<pre><code class="language-plaintext">SENDER_PRIVATE_KEY=suiprivatekey1xxx // Sender private key
RECEIVER_PRIVATE_KEY=suiprivatekey1xxx //Receiver private key
NETWORK=testnet
PACKAGE_ID=0x0xxxx // Package Id ที่เราได้จาก EP.4
</code></pre>
</li>
<li><p>สร้างไฟล์ <code>config.ts</code></p>
<pre><code class="language-typescript">import "dotenv/config";

// --- Env Checks ---
if (!process.env.SENDER_PRIVATE_KEY || !process.env.RECEIVER_PRIVATE_KEY) {
    throw new Error("Bro, you forgot the private keys in .env file.");
}

export const CONFIG = {
    // Keys
    SENDER_KEY: process.env.SENDER_PRIVATE_KEY,
    RECEIVER_KEY: process.env.RECEIVER_PRIVATE_KEY,

    // Network &amp; Package
    SUI_NETWORK: process.env.SUI_NETWORK || "testnet",
    PACKAGE_ID: process.env.PACKAGE_ID

    // Seal Key Servers (Testnet)
    KEY_SERVERS: [
        "0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75",
        "0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8",
    ],

    // Dummy 32-byte hex for initial encryption setup
    PLACEHOLDER_POLICY_ID: "0x" + "0".repeat(64),
};

// --- Walrus Endpoints ---
export const WALRUS_CONFIG = {
    EPOCHS: Number(process.env.WALRUS_EPOCHS ?? 1),

    PUBLISHERS: [
        "https://publisher.walrus-testnet.walrus.space",
        "https://wal-publisher-testnet.staketab.org",
        "https://walrus-testnet-publisher.redundex.com",
        "https://walrus-testnet-publisher.nodes.guru",
    ].filter(Boolean) as string[],

    AGGREGATORS: [
        "https://aggregator.walrus-testnet.walrus.space",
        "https://wal-aggregator-testnet.staketab.org",
        "https://walrus-testnet-aggregator.redundex.com",
        "https://walrus-testnet-aggregator.nodes.guru",
    ].filter(Boolean) as string[],
};
</code></pre>
<p> มีส่วนที่น่าสนใจเพิ่มขึ้นมาคือ <code>WALRUS_CONFIG</code> ซึ่งส่วนนี้จะมีทั้ง</p>
<ul>
<li><p>WALRUS_EPOCH เป็นการระบุอายุการเก็บรักษาข้อมูลนั้นเอง</p>
</li>
<li><p>PUBLISHERS คือ endpoint สำหรับใช้ในการอัพโหลดข้อมูลไปยัง Walrus network</p>
</li>
<li><p>AGGREGATORS คือ endpoint สำหรับการอ่านข้อมูลจาก Walrus netwokr</p>
</li>
</ul>
</li>
<li><p>สร้างไฟล์ <code>walrus.ts</code></p>
<pre><code class="language-typescript">import { WALRUS_CONFIG } from "./config";

/**
 * Uploads raw bytes to Walrus.
 * It tries multiple publisher nodes until one works.
 */
export async function uploadBlob(data: Uint8Array): Promise&lt;string&gt; {
    let lastError: unknown = null;
    for (const baseUrl of WALRUS_CONFIG.PUBLISHERS) {
        const url = `\({baseUrl}/v1/blobs?epochs=\){WALRUS_CONFIG.EPOCHS}`;
        console.log(`☁️  Trying upload to: ${baseUrl}...`);
        try {
            const res = await fetch(url, {
                method: "PUT",
                body: data,
            });

            if (!res.ok) {
                throw new Error(`HTTP \({res.status}: \){res.statusText}`);
            }
            const info = (await res.json()) as any;
            const blobId =
                info.newlyCreated?.blobObject?.blobId ||
                info.alreadyCertified?.blobId;

            if (!blobId) throw new Error("Invalid response format from Walrus");

            return blobId; // Success!
        } catch (e) {
            console.error(`-&gt; Failed at ${baseUrl}, trying next...`);
            lastError = e;
        }
    }
    throw new Error(`All Walrus publishers failed. RIP. Error: ${lastError}`);
}

/**
 * Downloads raw bytes from Walrus.
 * It tries multiple aggregator nodes until one works.
 */
export async function downloadBlob(blobId: string): Promise&lt;Uint8Array&gt; {
    let lastError: unknown = null;
    for (const baseUrl of WALRUS_CONFIG.AGGREGATORS) {
        const url = `\({baseUrl}/v1/blobs/\){blobId}`;
        console.log(`⬇️  Trying download from: ${baseUrl}...`);
        try {
            const res = await fetch(url);
            if (!res.ok) throw new Error(`HTTP ${res.status}`);

            const buffer = await res.arrayBuffer();
            return new Uint8Array(buffer); // Success!
        } catch (e) {
            console.error(`-&gt; Node ${baseUrl} failed, trying next...`);
            lastError = e;
        }
    }
    throw new Error(`All Walrus aggregators failed. Blob might be gone.`);
}
</code></pre>
<p> ตรงไปตรงมาเลย ก่อนหน้านี้เราต้องเรียกผ่าน Walrus library ในการอ่านเเละเขียนข้อมูลใช่ไหม ซึ่งเท่าที่ลองดูเจอปัญหางอแงบ้าง เลยตัดจบด้วยการใช้วิธีเรียกผ่าน HTTP Protocol เลยทีเดียวจบ</p>
</li>
<li><p>สร้างไฟล์ <code>main.ts</code></p>
<pre><code class="language-typescript">import { getFullnodeUrl, SuiClient } from "@mysten/sui/client";
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
import { SealClient, SessionKey, EncryptedObject } from "@mysten/seal";
import { Transaction } from "@mysten/sui/transactions";
import { fromHex, toHex } from "@mysten/sui/utils";
import { randomBytes } from "node:crypto";

import { CONFIG } from "./config";
import { uploadBlob, downloadBlob } from "./walrus";


let suiClient: SuiClient;
let sealClient: SealClient;

async function main() {
    console.log("🚀 Starting the Walrus x Seal Privacy Demo...");

    // 1. Setup Network
    await initClients();

    // 2. Setup Users
    const sender = getKeypair(CONFIG.SENDER_KEY);
    const receiver = getKeypair(CONFIG.RECEIVER_KEY);

    console.log(`👤 Sender:   ${sender.getPublicKey().toSuiAddress()}`);
    console.log(`👤 Receiver: ${receiver.getPublicKey().toSuiAddress()}`);

    const secretMessage = "Yo fam, this is exclusive content! 📸 (Secured by Seal)";

    // 3. Sender Flow
    const policyObjectId = await runSenderFlow(secretMessage, receiver, sender);

    // 4. Simulate Network Delay
    console.log("\n⏳ Chilling for 5s to let Walrus propagate...");
    await sleep(5000);

    // 5. Receiver Flow
    await runReceiverFlow(policyObjectId, receiver);

    console.log("\n✨ Boom! Mission accomplished.");
}

// ============================================================================
// WORKFLOWS
// ============================================================================

async function runSenderFlow(
    secret: string,
    recipient: Ed25519Keypair,
    sender: Ed25519Keypair
): Promise&lt;string&gt; {
    console.log(`\n--- [Step 1] Sender Turn ---`);
    console.log(`🔒 Encrypting payload...`);

    // Prepare a randomized ID for encryption
    // We add a nonce to ensure the ID is unique even if the policy ID is static
    const policyBytes = fromHex(CONFIG.PLACEHOLDER_POLICY_ID);
    const nonce = new Uint8Array(randomBytes(5));
    const idBytes = new Uint8Array(policyBytes.length + nonce.length);
    idBytes.set(policyBytes, 0);
    idBytes.set(nonce, policyBytes.length);

    // Encrypt locally
    const plainBytes = new TextEncoder().encode(secret);
    const { encryptedObject } = await sealClient.encrypt({
        threshold: 2,
        packageId: CONFIG.PACKAGE_ID,
        id: toHex(idBytes),
        data: plainBytes,
    });

    console.log(`-&gt; Encrypted size: ${encryptedObject.byteLength} bytes`);

    // Upload to Walrus (Using our helper)
    const blobId = await uploadBlob(encryptedObject);
    console.log(`✅ Walrus Blob ID: ${blobId}`);

    // Mint the Policy Object on Sui
    console.log("⛓️ Minting SecretMessage object on-chain...");
    const tx = new Transaction();
    tx.moveCall({
        target: `${CONFIG.PACKAGE_ID}::message::create_secret_message`,
        arguments: [
            tx.pure.address(recipient.getPublicKey().toSuiAddress()),
            tx.pure.string(blobId)
        ],
    });

    const result = await suiClient.signAndExecuteTransaction({
        signer: sender,
        transaction: tx,
        options: { showObjectChanges: true },
    });

    // Find the new object ID
    const created = result.objectChanges?.find(
        (c: any) =&gt;
            c.type === "created" &amp;&amp;
            "objectId" in c &amp;&amp;
            c.objectType.includes("::message::SecretMessage")
    );

    if (!created || !("objectId" in created)) {
        throw new Error("Tx failed. Could not find created object.");
    }

    const objectId = created.objectId as string;
    console.log(`📡 Policy Object created: ${objectId}`);

    return objectId;
}

async function runReceiverFlow(policyObjectId: string, recipient: Ed25519Keypair) {
    console.log(`\n--- [Step 2] Receiver Turn ---`);
    console.log(`📦 Fetching policy object: ${policyObjectId}`);

    // 1. Get Blob ID from Chain
    const obj = await suiClient.getObject({
        id: policyObjectId,
        options: { showContent: true },
    });

    const fields = (obj.data?.content as any)?.fields;
    if (!fields) throw new Error("Policy object is empty/invalid.");

    const blobId = fields.walrus_blob_id;
    console.log(`📦 Found Walrus ID: ${blobId}`);

    // 2. Download from Walrus (Using our helper)
    const encryptedBytes = await downloadBlob(blobId);
    console.log(`   -&gt; Got ${encryptedBytes.byteLength} bytes`);

    // 3. Validation
    const info = EncryptedObject.parse(encryptedBytes);
    if (!info.id || !info.threshold) throw new Error("Data looks corrupted.");

    // 4. Session Key (Ephemeral security)
    console.log(`🔑 Generating temp session key...`);
    const recipientAddr = recipient.getPublicKey().toSuiAddress();
    const sessionKey = await SessionKey.create({
        address: recipientAddr,
        packageId: CONFIG.PACKAGE_ID,
        ttlMin: 10,
        signer: recipient,
        suiClient,
    });

    // 5. Build Proof (seal_approve)
    console.log("🛡️  Building auth proof...");
    const tx = new Transaction();
    tx.setSender(recipientAddr);
    tx.moveCall({
        target: `${CONFIG.PACKAGE_ID}::message::seal_approve`,
        arguments: [
            tx.pure.vector("u8", fromHex(info.id)),
            tx.object(policyObjectId),
        ],
    });

    const txBytes = await tx.build({ client: suiClient, onlyTransactionKind: true });

    // 6. Decrypt
    console.log("🔓 Asking Key Servers to decrypt...");
    const decryptedBytes = await sealClient.decrypt({
        data: encryptedBytes,
        sessionKey,
        txBytes,
    });

    const msg = new TextDecoder().decode(decryptedBytes);
    console.log(`\n🎉 SUCCESS! Message: "${msg}"`);
}

async function initClients() {
    suiClient = new SuiClient({ url: getFullnodeUrl(CONFIG.SUI_NETWORK) });
    sealClient = new SealClient({
        suiClient,
        serverConfigs: CONFIG.KEY_SERVERS.map((id) =&gt; ({
            objectId: id,
            weight: 1,
        })),
        verifyKeyServers: false, // skipping strict verification for testnet demo
    });
}

function getKeypair(secretKey: string) {
    return Ed25519Keypair.fromSecretKey(decodeSuiPrivateKey(secretKey).secretKey);
}

function sleep(ms: number) {
    return new Promise((r) =&gt; setTimeout(r, ms));
}

main().catch((e) =&gt; {
    console.error("FATAL ERROR:", e);
    process.exit(1);
});
</code></pre>
<p> มาถึงไฟล์สุดท้าย พระเอกของเรานั้นเอง มาไล่ทีละขั้นตอนกันนะ</p>
<ul>
<li><p>เริ่มจากเป็นการ <code>initClients()</code> ก่อนส่วนนี้จะเห็นว่าไม่มีการเรียกใช้งาน Walrus แล้ว เพราะเราจะส่งข้อมูลผ่าน HTTP กัน</p>
</li>
<li><p>ส่วนของฟังก์ชั่น <code>runSenderFlow</code> นั้นจะเป็นนำข้อความของเราไปเข้ารหัสด้วย Seal หลังจากนั้นก็ทำการอัพโหลดข้อมูลไปยัง Walrus network นั้นเอง เเละเมื่อเราได้ <code>blobId</code> มาแล้วก็ทำการเรียกใช้งานฟังก์ชั่น Sui smart contract ที่เราได้เขียนไว้เเล้วเพื่อกำหนดผู้รับ</p>
</li>
<li><p>ส่วนของฟังก์ชั่น <code>runReceiverFlow</code> ทำตรงกันข้ามนั้นเองโดยนำ <code>blobId</code> ไปดาวโหลดข้อมูลจาก Walrus network ลงมาก่อนแล้วทำการถอดรหัสข้อมูลด้วย Seal หลังจากผู้รับต้องทำการเรียก Sui smart contract เพื่อยืนยันความเป็นเจ้าของนั้นเอง</p>
</li>
</ul>
</li>
</ol>
<p>เรียบร้อยแล้วง่ายไหม ง่ายมั๊ง ลองเอาไปรันดูนะครับ เเล้วเอาผลลัพธ์มาโชว์ด้วยนะ นี้คือตัวอย่างเล็กๆน้อยนะ ซึ่งสิ่งที่เราได้สร้างขึ้นมานั้นจะมี คุณสมบัติ</p>
<ul>
<li><p><strong>Client-Side Encryption</strong> - ข้อมูลเข้ารหัสบนอุปกรณ์ผู้ใช้</p>
</li>
<li><p><strong>Decentralized Storage</strong> - เก็บบน Walrus แบบกระจายศูนย์</p>
</li>
<li><p><strong>On-Chain Access Control</strong> - Smart Contract ควบคุมสิทธิ์การเข้าถึง</p>
</li>
<li><p><strong>Threshold Cryptography</strong> - ไม่มี Single Point of Failure</p>
</li>
<li><p><strong>Programmable Ownership</strong> - สิทธิ์เป็น Object ที่โอนย้ายได้</p>
</li>
</ul>
<p>ซึ่งจากคุณสมบัติเหล่านี้เราสามารถนำไปต่อยอดเป็น use case อื่นได้อีกมายเลยนะ เช่น</p>
<ul>
<li><p>Creator Platforms ทำพวก Subscription-based content ได้สบายๆ</p>
</li>
<li><p>Gaming Content ทำพวก Unlockable content ตามเงื่อนไข On-Chain หรือจะทำ Secret quests และ time-limited events ก็ได้นะ</p>
</li>
<li><p>AI &amp; Data Markets สาย AI ขาย AI models และ datasets แบบ encrypted</p>
</li>
<li><p>NFT Utilities ทำ NFT ที่มีคอนเทนต์จริงซ่อนอยู่ :P</p>
</li>
</ul>
<p>เอาละ ลองไปต่อยอดกันดูนะ ไว้เจอกันใหม่</p>
<p>ปิดท้ายด้วยเเหล่งข้อมูลที่สามารถศึกษาได้เพิ่ม</p>
<ul>
<li><p><a href="https://docs.sui.io/">Sui Documentation</a></p>
</li>
<li><p><a href="https://www.walrus.xyz/">Walrus Website</a></p>
</li>
<li><p><a href="https://seal-docs.wal.app/UsingSeal/">Seal Documentation</a></p>
</li>
<li><p><a href="https://github.com/MystenLabs/seal">Seal GitHub</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[EP 4: Seal กับ On-Chain Access Control]]></title><description><![CDATA[สวัสดีครับ เดินทางมาถึงตอนที่ 4 แล้วกับซี่รีย์ Walrus + Seal ถ้าหากใครยังไม่ได้อ่าน อ่านย้อนหลังได้ที่นี้นะครับ

EP 1: https://onthemove.contributiondao.com/ep-1-walrus-and-seal

EP 2: https://onthemove.contributiondao.com/ep-2-walrus-programmable-st...]]></description><link>https://onthemove.contributiondao.com/ep-4-seal-on-chain-access-control</link><guid isPermaLink="true">https://onthemove.contributiondao.com/ep-4-seal-on-chain-access-control</guid><category><![CDATA[Sui]]></category><category><![CDATA[onthemoveth]]></category><category><![CDATA[Seal]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Sun, 23 Nov 2025 13:11:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763903358943/cf4956aa-d994-4abf-be70-e58f95ffd3e4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>สวัสดีครับ เดินทางมาถึงตอนที่ 4 แล้วกับซี่รีย์ Walrus + Seal ถ้าหากใครยังไม่ได้อ่าน อ่านย้อนหลังได้ที่นี้นะครับ</p>
<ul>
<li><p>EP 1: <a target="_blank" href="https://onthemove.contributiondao.com/ep-1-walrus-and-seal">https://onthemove.contributiondao.com/ep-1-walrus-and-seal</a></p>
</li>
<li><p>EP 2: <a target="_blank" href="https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage">https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage</a></p>
</li>
<li><p>EP 3: <a target="_blank" href="https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message">https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message</a></p>
</li>
</ul>
<p>สำหรับตอนนี้ เรามาเขียนในส่วนของ Move Smart Contract กันนะ เพราะจากตอนที่แล้ว เราได้เขียน Seal ไว้เรียบร้อยแล้ว แต่ขาดในส่วนของ Smart Contract ในการจัดการเรื่องสิทธิในการเข้าถึง</p>
<p>โดย Seal จะปกป้องข้อมูลของเราจากผู้ใช้งานทั่วไป ด้วยระบบป้องกันดังต่อไปนี้</p>
<ol>
<li><p>Client-Side Encryption: หลักการสำคัญของ Seal คือข้อมูลที่ละเอียดอ่อนจะถูกเข้ารหัส บนอุปกรณ์ของเราเอง ก่อนที่จะถูกส่งออกไปที่อื่นๆ โดยไม่มีเซิร์ฟเวอร์ แม้แต่ key servers ของ Seal เอง ก็จะไม่เห็นข้อมูล plaintext ของเราเลย</p>
</li>
<li><p>Threshold Encryption: เพื่อหลีกเลี่ยงความเสี่ยงจากการจัดการกุญแจแบบรวมศูนย์ Seal ได้ใช้ Threshold Encryption แทนที่จะมีกุญแจถอดรหัสเพียงอันเดียว ซึ่งกุญแจนั้นจะถูกแบ่งออกเป็นหลาย shares และกระจายไปยังเครือข่าย Decentralized Key Servers ที่เป็นอิสระต่อกัน หากต้องการสร้างกุญแจ ต้นฉบับขึ้นมาใหม่เพื่อถอดรหัส จะต้องมี key servers จำนวนหนึ่ง (เช่น 2 ใน 3) ทำงานร่วมกัน ซึ่งขั้นตอนนี่จะช่วยขจัด single point of failure ออกไปได้เลยนั้นเอง</p>
</li>
<li><p>On-Chain Access Control: นี่คือส่วนที่ล้ำสมัยที่สุดของสถาปัตยกรรม Seal! โดยตรรกะที่กำหนดว่าใครได้รับอนุญาตให้ถอดรหัสข้อมูลนั้นไม่ได้ถูก hard-coded อยู่ใน off-chain server แต่ถูกกำหนดไว้ใน Move Smart Contract นั้นเอง ซึ่งข้อมูลนี้จะถูกเปิดเผยต่อสาธารณะและตรวจสอบได้บน Sui Network! สิ่งนี้ทำให้เราสามารถสร้าง Access Policies ที่ละเอียดและสามารถ Programmable ได้ เช่น เช่น token-gating (อนุญาตเฉพาะผู้ถือ NFT บางประเภท), time-locks หรือ role-based permissions ที่ซับซ้อน</p>
</li>
</ol>
<p>มาเริ่มกันเลย ต่อไปนี้เราจะลงในส่วนของ Technical แล้วนะ ในคือการเขียน Move Smart Contract หากใครยังไม่ได้เตรียมเครื่องไม้ เครื่องมือ ให้ย้อนกลับไปดูบทความด้านล่างนี้นะ</p>
<ul>
<li><a target="_blank" href="https://onthemove.contributiondao.com/walrus-simple-usage">https://onthemove.contributiondao.com/walrus-simple-usage</a></li>
</ul>
<p>โดย Move Smart Contract ที่เราจะเขียนกันต่อไปนี้ จะมีหัวใจหลักของ Seal อยู่ที่ฟังก์ชั่น <code>seal_approve</code> ซึ่ง</p>
<p>จุดเชื่อมต่อที่สำคัญสำหรับนักพัฒนาคือ <code>entry function</code> ที่มีชื่อเฉพาะใน Move module ของเรา: <code>seal_approve</code> ฟังก์ชันนี้ทำหน้าที่เป็น <strong>On-Chain Gatekeeper</strong> สำหรับคำขอถอดรหัสทั้งหลาย!</p>
<p>ซึ่งเราจะทดสอบเขียน <code>secret_message.move</code> contract ของเรา กันมาเริ่มกันทีละ step กันเลยครับ</p>
<ol>
<li><p>สร้างโปรเจ็คขึ้นมาใหม่</p>
<pre><code class="lang-bash"> sui move new secret_message
</code></pre>
</li>
<li><p>แก้ไข source code ในไฟล์ <code>secret_message.move</code></p>
<pre><code class="lang-rust"> module secret_message::message {
     <span class="hljs-comment">// Import เฉพาะตัวที่ต้องใช้จริง (module ส่วนใหญ่ Sui prelude มีให้แล้ว)</span>
     <span class="hljs-keyword">use</span> sui::object::UID;
     <span class="hljs-keyword">use</span> sui::tx_context::TxContext;
     <span class="hljs-keyword">use</span> std::string::<span class="hljs-built_in">String</span>;

     <span class="hljs-comment">/// Error ที่จะเกิดขึ้นเมื่อผู้ใช้ที่ไม่ได้รับอนุญาตพยายามเข้าถึงข้อความลับ</span>
     <span class="hljs-keyword">const</span> ENoAccess: <span class="hljs-built_in">u64</span> = <span class="hljs-number">0</span>;

     <span class="hljs-comment">/// โครงสร้างของ SecretMessage ซึ่งเป็น Policy Object</span>
     <span class="hljs-comment">///</span>
     <span class="hljs-comment">/// - Policy นี้ไม่เก็บข้อความจริงบน-chain</span>
     <span class="hljs-comment">/// - ตัวข้อความจะถูกเข้ารหัสและเก็บบน Walrus (off-chain)</span>
     <span class="hljs-comment">/// - On-chain จะเก็บเฉพาะ metadata และสิทธิ์การเข้าถึง</span>
     <span class="hljs-comment">///</span>
     <span class="hljs-comment">/// ฟิลด์:</span>
     <span class="hljs-comment">/// - `sender`: address ของผู้สร้าง (ผู้ส่งข้อความลับ)</span>
     <span class="hljs-comment">/// - `recipient`: address ของผู้รับที่ได้รับอนุญาต</span>
     <span class="hljs-comment">/// - `walrus_blob_id`: ID ของ blob ที่เก็บ encrypted content บน Walrus</span>
     public <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SecretMessage</span></span> has key, store {
         id: UID,
         sender: address,
         recipient: address,
         walrus_blob_id: <span class="hljs-built_in">String</span>,
     }

     <span class="hljs-comment">/// ฟังก์ชันสำหรับสร้าง SecretMessage ใหม่</span>
     <span class="hljs-comment">///</span>
     <span class="hljs-comment">/// ขั้นตอน:</span>
     <span class="hljs-comment">/// 1. ผู้ส่งเรียกฟังก์ชันนี้พร้อมระบุผู้รับ และ Walrus blob ID</span>
     <span class="hljs-comment">/// 2. ระบบสร้าง Policy Object ใหม่ (SecretMessage)</span>
     <span class="hljs-comment">/// 3. ส่ง Policy Object ไปยัง wallet ของผู้รับ</span>
     <span class="hljs-comment">///</span>
     <span class="hljs-comment">/// หมายเหตุ:</span>
     <span class="hljs-comment">/// - ผู้รับเป็นคนเดียวที่ถือ object นี้ → สอดคล้องกับ access control</span>
     public fun create_secret_message(
         recipient: address,
         walrus_blob_id: <span class="hljs-built_in">String</span>,
         ctx: &amp;<span class="hljs-keyword">mut</span> TxContext
     ) {
         <span class="hljs-keyword">let</span> message_policy = SecretMessage {
             id: object::new(ctx),
             sender: ctx.sender(),
             recipient,
             walrus_blob_id,
         };

         <span class="hljs-comment">// โอน Policy Object ไปที่ผู้รับ → เพื่อให้เป็นเจ้าของสิทธิ์การถอดรหัส</span>
         transfer::public_transfer(message_policy, recipient);
     }

     <span class="hljs-comment">/// ฟังก์ชันหลักสำหรับทำงานร่วมกับ Seal (Decentralized Access Control Service)</span>
     <span class="hljs-comment">///</span>
     <span class="hljs-comment">/// Concept:</span>
     <span class="hljs-comment">/// - Seal Key Servers จะจำลองการเรียกฟังก์ชันนี้ในขั้นตอน Verify Access</span>
     <span class="hljs-comment">/// - ถ้าฟังก์ชันนี้ "ผ่านเงื่อนไข" → Key Servers จะอนุญาตให้ user รับ key share</span>
     <span class="hljs-comment">/// - ถ้าไม่ผ่าน → จะไม่ปลดล็อก key share</span>
     <span class="hljs-comment">///</span>
     <span class="hljs-comment">/// มันคือ "On-chain Access Control Policy"</span>
     <span class="hljs-comment">/// ใช้เงื่อนไขบน-chain ตรวจสอบว่าผู้ที่ขอถอดรหัสเป็น "recipient" ตัวจริงหรือไม่</span>
     <span class="hljs-comment">///</span>
     <span class="hljs-comment">/// พารามิเตอร์:</span>
     <span class="hljs-comment">/// - `_`: ค่า vector&lt;u8&gt; ที่ Seal ต้องส่งมา (แต่ในที่นี้ไม่ใช้งาน)</span>
     <span class="hljs-comment">/// - `policy`: reference ไปยัง SecretMessage ของข้อความนั้น</span>
     <span class="hljs-comment">/// - `ctx`: ใช้ตรวจสอบว่า user ที่เรียกคือใคร</span>
     public fun seal_approve(
         _: vector&lt;<span class="hljs-built_in">u8</span>&gt;,
         policy: &amp;SecretMessage,
         ctx: &amp;TxContext
     ) {
         <span class="hljs-comment">// อนุญาตเฉพาะผู้รับที่กำหนดไว้ใน policy เท่านั้น</span>
         <span class="hljs-comment">// ถ้า ctx.sender() ไม่ใช่ recipient → throw error</span>
         <span class="hljs-built_in">assert!</span>(ctx.sender() == policy.recipient, ENoAccess);
     }
 }
</code></pre>
</li>
<li><p>Deploy Move Smart Contract</p>
<p> เราเริ่มจากการทดสอบ Build กันก่อนนะ ว่าติดปัญหาอะไรไหม อย่าลืมสลับไปใช้งาน Testnet นะครับ</p>
<pre><code class="lang-rust"> sui <span class="hljs-keyword">move</span> build
</code></pre>
<p> หากทุกอย่างเรียบร้อยก็เตรียมไปสู่ขั้นตอนการ Deploy ไปยัง SUI Testnet ได้เลย</p>
<pre><code class="lang-rust"> sui client publish --gas-budget <span class="hljs-number">100000000</span>
</code></pre>
<p> เราจะได้หน้าตาประมาณนี้นะ</p>
<pre><code class="lang-bash"> ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
 │ Object Changes                                                                                   │
 ├──────────────────────────────────────────────────────────────────────────────────────────────────┤
 │ Created Objects:                                                                                 │
 │  ┌──                                                                                             │
 │  │ ObjectID: 0x3e841ab9ffb9dd26ef128631030e2fed4ce19d40e2be1521e760fd4c2dbfe18b                  │
 │  │ Sender: 0xc1a83e6ed8a891cc891208e4288290013ed3f1ebf1576eede3f2c7bc4c56e75c                    │
 │  │ Owner: Account Address ( 0xc1a83e6ed8a891cc891208e4288290013ed3f1ebf1576eede3f2c7bc4c56e75c ) │
 │  │ ObjectType: 0x2::package::UpgradeCap                                                          │
 │  │ Version: 349180711                                                                            │
 │  │ Digest: 4zJPCm9Xab7VkY2bfsH347FwFcPQo74rYN75Qz9dddzk                                          │
 │  └──                                                                                             │
 │ Mutated Objects:                                                                                 │
 │  ┌──                                                                                             │
 │  │ ObjectID: 0x0e77d7f999871c0a82f098699b0128715ade05416531deee0902f8a52bee4f9e                  │
 │  │ Sender: 0xc1a83e6ed8a891cc891208e4288290013ed3f1ebf1576eede3f2c7bc4c56e75c                    │
 │  │ Owner: Account Address ( 0xc1a83e6ed8a891cc891208e4288290013ed3f1ebf1576eede3f2c7bc4c56e75c ) │
 │  │ ObjectType: 0x2::coin::Coin&lt;0x2::sui::SUI&gt;                                                    │
 │  │ Version: 349180711                                                                            │
 │  │ Digest: G8GFetS11dJDiDPYJvnvVaJ3kZnvy3ec31CPJfVSZTnu                                          │
 │  └──                                                                                             │
 │ Published Objects:                                                                               │
 │  ┌──                                                                                             │
 │  │ PackageID: 0x04c285b7f84f9db5cbef6e3c0a48a086463fa618a45ddc14154179fbde533c34                 │
 │  │ Version: 1                                                                                    │
 │  │ Digest: AFZExHfnygn3MqDCkfK1Hh9Ht5aaSxuGHu3bovjvbMhG                                          │
 │  │ Modules: message                                                                              │
 │  └──                                                                                             │
 ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
 ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
 │ Balance Changes                                                                                   │
 ├───────────────────────────────────────────────────────────────────────────────────────────────────┤
 │  ┌──                                                                                              │
 │  │ Owner: Account Address ( 0xc1a83e6ed8a891cc891208e4288290013ed3f1ebf1576eede3f2c7bc4c56e75c )  │
 │  │ CoinType: 0x2::sui::SUI                                                                        │
 │  │ Amount: -8488280                                                                               │
 │  └──                                                                                              │
 ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯
</code></pre>
<p> ซึ่งสิ่งที่สำคัญคือ หลังจากที่เราได้ทำการ Deploy สำเร็จเเล้ว เราจะเห็น <code>PackageID</code> เป็น String ยาวๆ <code>0x...</code> ให้เก็บ <code>PackageID</code> นั้นเอาไว้ เพราะเราจะต้องนำไปใช้ใน EP 3 ซึ่งยังจำกันได้ใช่ไหมว่า ค่าของ <code>PackageID</code> เรายังเป็นค่าที่ยังไม่ได้กำหนด ซึ่งทำให้ตัวอย่างของเราทำงานไม่ได้</p>
<p> ซึ่งนี้ที่ <code>PackageID</code> ของเราคือ</p>
<pre><code class="lang-bash"> 0x04c285b7f84f9db5cbef6e3c0a48a086463fa618a45ddc14154179fbde533c34
</code></pre>
</li>
</ol>
<p>เรียบร้อยครับ ตอนนี้เราได้ <code>PackageID</code> ที่พร้อมจะนำไปใช้งานแล้ว ก่อนที่เราจะไปกันต่อ ขอธิบายเพิ่มเกี่ยวกับการทำงานของ <code>seal_approve</code> กันสักนิดนึง โดยหลักการจะมีประมาณนี้นะ</p>
<ol>
<li><p><strong>ผู้ใช้ต้องการถอดรหัส</strong>: <code>Client application</code> ของผู้ใช้ (เช่น เว็บไซต์ Only… ของเรา) ต้องการเข้าถึงคอนเทนต์ลับ</p>
</li>
<li><p><strong>จำลองธุรกรรม</strong>: แทนที่จะส่ง <code>transaction</code> ไปรันบน Sui Network ทันที <code>Client application</code> จะสร้าง <code>Sui transaction</code> ที่เรียกฟังก์ชัน <code>seal_approve</code> นี้ (แต่ยังไม่ <code>execute</code> จริง!) แล้วส่ง <code>serialized bytes</code> ของ <code>transaction</code> นี้ไปให้ <code>Seal Key Servers</code></p>
</li>
<li><p><code>Key Servers</code> ตรวจสอบ: <code>Key Servers</code> หลายตัว จะทำการจำลองการรัน <code>transaction</code> นี้บน Sui แยกกัน</p>
</li>
<li><p><strong>ปล่อย Key Shares (ถ้าผ่าน)</strong>: ถ้าการจำลองสำเร็จ (คือ <code>assert!</code> ในฟังก์ชัน <code>seal_approve</code> เป็นจริง เช่น <code>ctx.sender()</code> คือ <code>recipient</code> ที่ระบุไว้) <code>Key Server</code> นั้นๆ ก็จะปล่อย <code>key share</code> ของตนออกมา</p>
</li>
<li><p><strong>ปฏิเสธ (ถ้าไม่ผ่าน)</strong>: แต่ถ้าการจำลองล้มเหลว (เช่น <code>assert!</code> เป็นเท็จ เพราะผู้ที่พยายามถอดรหัสไม่ใช่ <code>recipient</code> ที่ถูกต้อง) <code>Key Server</code> ก็จะปฏิเสธที่จะให้ <code>key share</code></p>
</li>
</ol>
<p>Sui ถูกใช้เป็น decentralized และ verifiable access-control oracle ได้อย่างชาญฉลาด! ข้อมูลที่เข้ารหัสสามารถเก็บไว้ที่ไหนก็ได้ — ไม่ว่าจะเป็น Walrus หรือเครือข่ายอื่น—เพราะฟังก์ชัน seal_approve ทำหน้าที่เป็น public, immutable, composable API สำหรับตรวจสอบสิทธิ์การเข้าถึงบน chain โดยตรง</p>
<p>พูดง่ายๆ คือ: ตัว blockchain กลายเป็น access-control engine ที่ทุกระบบเชื่อถือร่วมกันได้ เป็นดีไซน์ที่ทั้งเรียบง่ายและทรงพลังมากๆ เลยใช่ไหมล่ะ?</p>
<p>เราเตรียมทุกอย่างพร้อมแล้ว สัปดาห์เราจะมาประกอบร่างสุดท้ายกันแล้ว อย่าลืมไปทดสอบ Deploy กันเด้อ แล้วเจอกันใหม่นะ</p>
]]></content:encoded></item><item><title><![CDATA[EP 3: Walrus และ Seal เข้ารหัสข้อมูล สำหรับเนื้อหาสุดพิเศษ]]></title><description><![CDATA[สวัสดีครับ หายไปกันไปนาน 2-3 สัปดาห์ สาเหตุนั้นมีเพียงหนึ่งเดียวนั้นก็คือ งานเยอะจ้า ไม่รู้จะเยอะไปไหน ไม่ไหวจะเคลีย บทความเลยดองไว้สะนานเลย วันนี้กลับมาต่อกันละนะ ไม่แน่ใจว่าลืมกันไปแล้วยังกับ 2 EP ก่อนหน้านั้น

https://onthemove.contributiondao.com...]]></description><link>https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message</link><guid isPermaLink="true">https://onthemove.contributiondao.com/ep-3-walrus-seal-send-secret-message</guid><category><![CDATA[onthemoveth]]></category><category><![CDATA[Sui]]></category><category><![CDATA[walrus]]></category><category><![CDATA[Seal]]></category><category><![CDATA[encryption]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Sat, 15 Nov 2025 16:58:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763226993454/c34e0b99-406a-4383-8a88-8519def7355f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>สวัสดีครับ หายไปกันไปนาน 2-3 สัปดาห์ สาเหตุนั้นมีเพียงหนึ่งเดียวนั้นก็คือ งานเยอะจ้า ไม่รู้จะเยอะไปไหน ไม่ไหวจะเคลีย บทความเลยดองไว้สะนานเลย วันนี้กลับมาต่อกันละนะ ไม่แน่ใจว่าลืมกันไปแล้วยังกับ 2 EP ก่อนหน้านั้น</p>
<ul>
<li><p><a target="_blank" href="https://onthemove.contributiondao.com/ep-1-walrus-and-seal">https://onthemove.contributiondao.com/ep-1-walrus-and-seal</a></p>
</li>
<li><p><a target="_blank" href="https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage">https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage</a></p>
</li>
</ul>
<p>ไปทบทวนกันก่อนนะ สำหรับ EP นี้เราจะไปต่อกันที่การนำ Seal มาใช้ในการเข้าหรัสข้อมูลของเรานั้นเอง ซึ่งอย่างที่รู้กันว่า Blockchain ทุกคนสามารถเข้าถึงและตรวจสอบได้ แต่กลายเป็นปัญหาใหญ่สำหรับแอปพลิเคชันที่ต้องจัดการข้อมูลที่ละเอียดอ่อน เช่น ข้อความส่วนตัว, ข้อมูลที่เป็นกรรมสิทธิ์, หรือ <strong>คอนเทนต์ลับ</strong> ของเรา! ถ้าทุกคนเห็นได้หมด ก็ไม่ Exclusive อะดิ</p>
<p>นี่แหละคือที่มาของ <strong>Seal</strong> ที่เราสามารถสร้างความเป็นส่วนตัว (Privacy) และการควบคุมการเข้าถึง (Access Control) บน Sui network ได้</p>
<hr />
<p><strong>Seal คืออะไร?</strong></p>
<p><strong>Seal</strong> คือ <em>Decentralized Secrets Management (DSM)</em> ที่ช่วยให้นักพัฒนาจัดการข้อมูลลับ (Secrets) ที่ถูกเข้ารหัสไว้ได้อย่างปลอดภัย โดยใช้ Key Servers บนเครือข่าย <strong>Sui Network</strong> เพื่อกำหนดและบังคับใช้นโยบายการเข้าถึงข้อมูลลับแบบ Trust-Minimized</p>
<p>Seal ออกแบบมาเพื่อให้:</p>
<ul>
<li><p>ได้ Privacy แบบเดิม: เหมือนระบบเข้ารหัสข้อมูลทั่วไป</p>
</li>
<li><p>ได้ Security แบบ Web3: ไม่มี single point of failure, ตรวจสอบได้, trust-minimized</p>
</li>
<li><p>สร้าง dApps ที่ยังคงความ Private ได้ บนระบบ Decentralized เต็มรูปแบบ</p>
</li>
</ul>
<p>พูดง่าย ๆ คือ Seal = ความลับ + Decentralized + ควบคุมสิทธิ์ด้วย Smart Contract</p>
<p>ซึ่งด้วยความสามารถของ Seal นักพัฒนาก็จะสามารถสร้าง dApps ที่ยังมีความเป็นส่วนตัวของผู้ใช้ได้โดยไม่ต้องทิ้งความเป็น Decentralized ไปนั้นเอง</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763225126468/a180e825-017d-45ba-83cb-a76d0520e373.png" alt="ภาพการทำงานของ Seal SDK" class="image--center mx-auto" /></p>
<p><strong>ทำไมต้องใช้ Seal + Walrus คู่กัน?</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>ระบบ</td><td>หน้าที่</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Seal</strong></td><td>เข้ารหัสข้อมูลลับ + บังคับนโยบายผู้เข้าถึง</td></tr>
<tr>
<td><strong>Walrus</strong></td><td>เก็บ payload ที่เข้ารหัสแบบ decentralized</td></tr>
</tbody>
</table>
</div><p>พอละไปลุยกันเลยดีกว่า ไปเขียนโค๊ดกันเถอะ</p>
<p><strong>เริ่มต้นใช้งาน Seal SDK</strong></p>
<ol>
<li><p>ติดตั้ง Seal SDK</p>
<p> ต้องติดตั้ง Seal SDK ก่อนเริ่มใช้งานนะ</p>
<pre><code class="lang-bash"> npm install @mysten/seal
</code></pre>
</li>
<li><p>จากนั้นแก้ไขไฟล์ <code>index.ts</code> ของเราเพื่อเพิ่ม <code>SealClient</code> และตั้งค่า <code>Public Key Servers</code> สำหรับ <code>Testnet</code> นอกจากนี้เราจะสร้าง <code>keypair</code> เพิ่มอีกหนึ่งคู่สำหรับจำลองเป็น "ผู้รับ" คอนเทนต์ของเรา:</p>
<pre><code class="lang-typescript"> <span class="hljs-comment">// index.ts (เพิ่มโค้ดส่วนนี้)</span>
 <span class="hljs-keyword">import</span> { SealClient, SessionKey, EncryptedObject } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/seal"</span>;
 <span class="hljs-keyword">import</span> { Transaction } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/transactions"</span>;
 <span class="hljs-keyword">import</span> { fromHex } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/utils"</span>;

 <span class="hljs-comment">// Public key server object IDs สำหรับ Sui Testnet (นี่คือ Key Servers ที่จะช่วยจัดการกุญแจ)</span>
 <span class="hljs-keyword">const</span> KEY_SERVER_LIST_TESTNET = [
   <span class="hljs-string">"0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75"</span>,
   <span class="hljs-string">"0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8"</span>,
 ];

 <span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span> แทนที่ด้วย Package ID ของ Smart Contract Move ที่เราจะ deploy ในอนาคต</span>
 <span class="hljs-comment">// ตอนนี้อาจจะยังไม่มี ก็ใช้ '0x...' ไปก่อนได้</span>
 <span class="hljs-keyword">const</span> PACKAGE_ID = <span class="hljs-string">"0x..."</span>;

 <span class="hljs-comment">// ... โค้ดส่วน main() </span>
 <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
 <span class="hljs-comment">// สร้าง SealClient ของเรา</span>
   <span class="hljs-keyword">const</span> sealClient = <span class="hljs-keyword">new</span> SealClient({
     suiClient: suiClient,
     serverConfigs: KEY_SERVER_LIST_TESTNET.map(<span class="hljs-function">(<span class="hljs-params">id</span>) =&gt;</span> ({
       objectId: id,
       weight: <span class="hljs-number">1</span>,
     })),
     verifyKeyServers: <span class="hljs-literal">true</span>, <span class="hljs-comment">// ตรวจสอบความถูกต้องของ Key Servers</span>
   });
 }
</code></pre>
</li>
<li><p>สร้าง ฟังก์ชัน ในการเข้ารหัสข้อมูล</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendSecretMessage</span>(<span class="hljs-params">params: { secret: <span class="hljs-built_in">string</span>; recipient: <span class="hljs-built_in">string</span>; sender: Ed25519Keypair; suiClient: SuiClient; walrusClient: <span class="hljs-built_in">any</span>; sealClient: SealClient }</span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">string</span>&gt; </span>{
     <span class="hljs-keyword">const</span> { secret, recipient, sender, suiClient, walrusClient, sealClient } = params;

     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`\nกำลังเข้ารหัสคอนเทนต์ลับสำหรับ <span class="hljs-subst">${recipient}</span>...`</span>);
     <span class="hljs-keyword">const</span> placeholderPolicyId = <span class="hljs-string">"0x"</span> + <span class="hljs-string">"0"</span>.repeat(<span class="hljs-number">64</span>);

     <span class="hljs-keyword">const</span> { encryptedObject } = <span class="hljs-keyword">await</span> sealClient.encrypt({
         threshold: <span class="hljs-number">2</span>,
         packageId: PACKAGE_ID,
         id: placeholderPolicyId,
         data: <span class="hljs-keyword">new</span> TextEncoder().encode(secret),
     });

     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"กำลังอัปโหลดข้อมูลที่เข้ารหัสไป Walrus..."</span>);
     <span class="hljs-keyword">const</span> walrusFile = WalrusFile.from({ contents: encryptedObject });
     <span class="hljs-keyword">const</span> writeResult = <span class="hljs-keyword">await</span> walrusClient.walrus.writeFiles({
         files: [walrusFile],
         signer: sender,
         epochs: <span class="hljs-number">1</span>,
         deletable: <span class="hljs-literal">false</span>,
     });

     <span class="hljs-keyword">const</span> blobId = writeResult[<span class="hljs-number">0</span>]?.blobId;
     <span class="hljs-keyword">if</span> (!blobId) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Failed to upload encrypted blob to Walrus"</span>);

     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Blob ที่เข้ารหัสถูกจัดเก็บแล้ว. Blob ID: <span class="hljs-subst">${blobId}</span>`</span>);

     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"กำลังสร้าง SecretMessage Policy Object บนเชน..."</span>);
     <span class="hljs-keyword">const</span> tx = <span class="hljs-keyword">new</span> Transaction();
     tx.moveCall({
         target: <span class="hljs-string">`<span class="hljs-subst">${PACKAGE_ID}</span>::message::create_secret_message`</span>,
         <span class="hljs-built_in">arguments</span>: [tx.pure.address(recipient), tx.pure.string(blobId)],
     });
     <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> suiClient.signAndExecuteTransaction({
         signer: sender,
         transaction: tx,
         options: { showObjectChanges: <span class="hljs-literal">true</span> },
     });
     <span class="hljs-keyword">const</span> created = result.objectChanges?.find(<span class="hljs-function">(<span class="hljs-params">c</span>) =&gt;</span> c.type === <span class="hljs-string">"created"</span> &amp;&amp; <span class="hljs-string">"objectId"</span> <span class="hljs-keyword">in</span> c &amp;&amp; c.objectType.includes(<span class="hljs-string">"secret_message::message::SecretMessage"</span>));
     <span class="hljs-keyword">if</span> (!created || !(<span class="hljs-string">"objectId"</span> <span class="hljs-keyword">in</span> created)) {
         <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"SecretMessage object was not created"</span>);
     }
     <span class="hljs-keyword">return</span> created.objectId;
 }
</code></pre>
</li>
<li><p>เมื่อต้องการเรียกใช้งานเราสามารถเพิ่ม ฟังก์ชัน ในการใช้งานดังนี้</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
  ...
  ...
  ...
  <span class="hljs-keyword">const</span> secret = <span class="hljs-string">"My super secret message for you ❤️"</span>;
  <span class="hljs-keyword">const</span> policyObjectId = <span class="hljs-keyword">await</span> sendSecretMessage({
     secret,
     recipient,
     sender: keypair,
     suiClient,
     walrusClient,
     sealClient,
   });
   <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`✅ SecretMessage policy object id: <span class="hljs-subst">${policyObjectId}</span>`</span>);
 }
</code></pre>
<p> สำหรับ <code>recipient</code> นั้นคือ address ของผู้รับนั้นเอง เราสามารถกำหนดได้เลยนะว่าจะส่งไปยัง address ไหน</p>
</li>
</ol>
<p>เรียบร้อบ เราได้ฟังก์ชั่น ในการส่งข้อความเเบบเข้ารหัสไปยังปลายทางแล้วครับ แต่บอกไว้ก่อนเลยตัวอย่างโค๊ดชุดนี้ยังทำงานไม่ได้นะ :P เเน่ละสิ เพราะยังไม่ได้เขียน Move Smart Contract เลยนิ ทำให้เรายังไม่มี Package ID นั้นเอง ถ้านำไปทดสอบและ เรียกใช้งาน มันจะเเสดงข้อความ Error ทันที รอติดตาม EP นะ เราจะไปเขียน Move Smart Contract กัน</p>
]]></content:encoded></item><item><title><![CDATA[EP 2: Walrus กับพลังแห่ง Programmable Storage!]]></title><description><![CDATA[เอาล่ะ! เมื่อกี้เราได้ลองอัปโหลดไฟล์ไป Walrus กันแล้วใช่มั้ย? แต่สิ่งที่ทำให้ Walrus ล้ำกว่า Decentralized Storage ทั่วไปคืออะไร? นั่นคือคุณสมบัติที่จะเปลี่ยน ทุกสิ่ง กับ Programmable Storage ครับ!
ส่วนใครยังไม่ได้ตามบทความที่แล้ว เปิดวาปให้ครับ

htt...]]></description><link>https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage</link><guid isPermaLink="true">https://onthemove.contributiondao.com/ep-2-walrus-programmable-storage</guid><category><![CDATA[onthemoveth]]></category><category><![CDATA[Sui]]></category><category><![CDATA[walrus]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Thu, 23 Oct 2025 14:36:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761230647455/9d613b91-33cc-4222-852d-2c9c52325139.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>เอาล่ะ! เมื่อกี้เราได้ลองอัปโหลดไฟล์ไป Walrus กันแล้วใช่มั้ย? แต่สิ่งที่ทำให้ Walrus ล้ำกว่า Decentralized Storage ทั่วไปคืออะไร? นั่นคือคุณสมบัติที่จะเปลี่ยน ทุกสิ่ง กับ <strong>Programmable Storage</strong> ครับ!</p>
<p>ส่วนใครยังไม่ได้ตามบทความที่แล้ว เปิดวาปให้ครับ</p>
<ul>
<li><a target="_blank" href="https://onthemove.contributiondao.com/ep-1-walrus-and-seal">https://onthemove.contributiondao.com/ep-1-walrus-and-seal</a></li>
</ul>
<p><strong>ปัญหาเดิมๆ ของบล็อกเชนทั่วไป</strong>:<br />บนแพลตฟอร์ม Blockchain อย่าง Ethereum ที่ใช้ <code>account-based model</code> เวลาเราเก็บข้อมูล (แม้จะใช้ IPFS หรือ Arweave) Smart Contract ของเราจะได้แค่ ตัวชี้ ง่ายๆ (เช่น <code>IPFS CID</code>) ซึ่งก็คือ String ธรรมดาๆ เท่านั้นเอง ตัวบล็อกเชนเองไม่รู้หรอกว่า <code>String</code> นั้นหมายถึงอะไร หรือสถานะของข้อมูลที่เก็บอยู่เป็นยังไง มันจึง ไร้ความสามารถ</p>
<p><strong>Walrus เลยพลิกเกมด้วย Object-Centric Model ของ Sui</strong></p>
<p>Walrus สร้างความแตกต่างโดยการใช้ Structure <strong>Object-Centric Model</strong> ของ Sui โดยเมื่อเราจัดเก็บไฟล์บน Walrus เราจะไม่ได้แค่ได้ <code>off-chain data fragments</code> เท่านั้น แต่เรายังได้ <strong>Sui Object</strong> ที่มีสถานะอยู่บนเชนเลย โดย <code>Sui Object</code> นี้จะเก็บ <code>metadata</code> และ <code>proof of availability</code> ของ <code>blob</code> นั้นๆเอาไว้ด้วยนั้นเอง</p>
<p>ลองดูตัวอย่างโครงสร้างของ Walrus Object ในภาษา Move กัน</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">WalrusBlob</span></span> has key, store {
    id: UID, <span class="hljs-comment">// ID เฉพาะของ Object นี้บน Sui</span>
    blob_id: <span class="hljs-built_in">String</span>, <span class="hljs-comment">// Unique Walrus ID สำหรับข้อมูล off-chain ของเรา</span>
    owner: address,  <span class="hljs-comment">// ใครเป็นเจ้าของ Object นี้</span>
    storage_epochs: <span class="hljs-built_in">u64</span>, <span class="hljs-comment">// ระยะเวลาจัดเก็บที่เหลืออยู่ (เป็นจำนวน Epochs)</span>
    <span class="hljs-comment">// ... อาจมี metadata อื่นๆ เช่น ขนาดไฟล์, ประเภทไฟล์</span>
}
</code></pre>
<p>เพราะว่า <code>storage receipt</code> ตัวนี้เป็น <code>Sui object</code> ดั้งเดิม มันจึงสามารถ <strong>เป็นเจ้าของ, โอนย้าย, และจัดการได้โดย Move Smart Contracts</strong> เหมือนกับสินทรัพย์ดิจิทัลอื่นๆ! สิ่งนี้เปลี่ยนวิธีการจัดเก็บข้อมูลอย่างสิ้นเชิง! เราเคยเกริ่นเเละยกตัวอย่างไปแล้วถึงคุณสมบัติของ Move Object ตามไปอ่านกันได้ที่นี้นะ</p>
<ul>
<li><p><a target="_blank" href="https://onthemove.contributiondao.com/ep-1-game-item-with-sui-object">https://onthemove.contributiondao.com/ep-1-game-item-with-sui-object</a></p>
</li>
<li><p><a target="_blank" href="https://onthemove.contributiondao.com/ep-2-game-item-with-sui-object">https://onthemove.contributiondao.com/ep-2-game-item-with-sui-object</a></p>
</li>
</ul>
<p><strong>ทำให้นักพัฒนาจะสามารถสร้างแอปพลิเคชันที่</strong></p>
<ul>
<li><p><strong>เป็นเจ้าของและโอนย้ายได้ (Own and Transfer it)</strong>: สร้าง <strong>Data Marketplaces</strong> ที่สามารถซื้อขายสิทธิ์การเข้าถึงชุดข้อมูลขนาดใหญ่ได้ง่ายๆ แค่โอนย้าย <code>WalrusBlob object</code> ไปยังผู้ซื้อเมื่อชำระเงินสำเร็จ</p>
</li>
<li><p><strong>ใช้ใน DeFi (Use it in DeFi)</strong>: นำ <code>Sui object</code> ที่แสดงถึงชุดข้อมูลอันมีค่าไปใช้เป็นหลักประกัน (Collateral) ใน Decentralized Lending Protocol</p>
</li>
<li><p><strong>ทำให้เป็นอัตโนมัติ (Automate it)</strong>: เขียน <code>Smart Contract</code> เพื่อขยายระยะเวลาจัดเก็บ <code>blob</code> โดยอัตโนมัติ ด้วยการจ่าย WAL tokens เพิ่ม หรือแม้แต่สั่งลบเพื่อเรียกคืนค่าจัดเก็บได้ตามเงื่อนไขก็ได้</p>
</li>
</ul>
<p>Walrus เลยไม่ได้ทำหน้าแค่จัดเก็บไฟล์ แต่กำลังสร้าง <code>on-chain, data-backed assets</code> รูปแบบใหม่! และนี่คือตัวอย่างของ Programmable Storage ที่จะเปลี่ยนการจัดเก็บข้อมูลจากแบบ Passive ไปสู่ สินทรัพย์ ที่สร้างรายได้แบบ Dynamic ได้เลยนะ!</p>
<p>ขอตัดจบกันแค่นี้ก่อน…​ (งานเยอะจัดช่วงนี้) เอาแค่หลักการไปก่อนนะ สำหรับใครอยากเห็นตัวอย่างติดตามกันต่อได้ใน EP.3 4 5 6 :)</p>
]]></content:encoded></item><item><title><![CDATA[EP 1: Web 3.0 Only... ด้วย Walrus และ Seal]]></title><description><![CDATA[สวัสดี ห่างหายกันไปหลายวัน ย้อนความไปนิดนึง จากความเดิมตอนที่แล้ว เราได้ทำการ Deploy website อย่างง่ายไปบน Walrus ไปแล้ว ยังจำได้ไหม ถ้าใครยังไม่ได้ลองเล่น ลองทำตามนี้ดูนะ https://onthemoveth.hashnode.dev/walrus-simple-usage
ในวันนี้เราจะไปให้ลึกขึ้น...]]></description><link>https://onthemove.contributiondao.com/ep-1-walrus-and-seal</link><guid isPermaLink="true">https://onthemove.contributiondao.com/ep-1-walrus-and-seal</guid><category><![CDATA[walrus]]></category><category><![CDATA[Seal]]></category><category><![CDATA[onthemoveth]]></category><category><![CDATA[Sui]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Sun, 19 Oct 2025 10:14:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760868762420/91afcf4f-8c38-4de6-90fb-978560943459.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>สวัสดี ห่างหายกันไปหลายวัน ย้อนความไปนิดนึง จากความเดิมตอนที่แล้ว เราได้ทำการ Deploy website อย่างง่ายไปบน Walrus ไปแล้ว ยังจำได้ไหม ถ้าใครยังไม่ได้ลองเล่น ลองทำตามนี้ดูนะ <a target="_blank" href="https://onthemoveth.hashnode.dev/walrus-simple-usage">https://onthemoveth.hashnode.dev/walrus-simple-usage</a></p>
<p>ในวันนี้เราจะไปให้ลึกขึ้นกว่าเดิม นั้นคือ….เราจะมาสอนทำ OnlyFans บน Web3 ด้วย Walrus + Seal หวังว่าคอนเทนต์จะไม่ติดเหลืองนะ :P (หลาย EP หน่อยนะ)</p>
<p>ลองแอบไปส่องกันได้ที่ <a target="_blank" href="https://only-fins.wal.app/">https://only-fins.wal.app/</a></p>
<p>พร้อมแล้วไปลุยกันเลย</p>
<p>อย่างที่ทุกท่านทราบกันดีว่า… การเก็บข้อมูลขนาดใหญ่บน Blockchain นั้นทั้งแพงและช้าสุดๆ ด้วยเหตุนี้เอง Sui จึงได้พัฒนา Walrus ขึ้นมาเพื่อแก้ปัญหานี้โดย Walrus เป็น Decentralized Storage ที่เป็นมากกว่าแค่ ที่เก็บข้อมูล เพราะหัวใจสำคัญคือ ข้อมูลที่เก็บไว้สามารถนำไปใช้ใน Smart Contracts ได้โดยตรง!</p>
<p>แต่ปัญหาที่ตามมาคือ… ถึงแม้ข้อมูลจะอยู่บน Storage แบบกระจายศูนย์แล้ว แต่บน Blockchain ทุกคนสามารถเข้าถึงข้อมูลได้ แล้วเราจะสร้าง Content แบบ Exclusive ที่ให้สิทธิ์เฉพาะคนที่จ่ายเงินเข้ามาดูเท่านั้นได้ยังไง?</p>
<p>คำตอบก็คือ Seal 🔒 ครับ! ส่วน Seal คืออะไรนั้นไว้เรามาพูดกันในตอนถัดไปนะ</p>
<p>ในซีรีส์นี้ เราจะมาลงมือทำไปพร้อมๆ กัน ตั้งแต่การอัปโหลดไฟล์ไปที่ Walrus ไปจนถึงการใช้ Seal สร้างระบบ Subscription แบบ On-chain กันเลย!</p>
<p>ก่อนจะไปถึงขั้นตอนการสร้างระบบ Subscription สุดปัง เรามาเริ่มจากการทดลองอัปโหลดไฟล์ ลับ ของเราไปที่ Walrus Testnet กันก่อนดีกว่า</p>
<p><strong>เริ่มจากการเตรียมเครื่องมือให้พร้อม</strong></p>
<ol>
<li><p>ติดตั้ง Rust: เนื่องจาก Sui Binaries ถูกเขียนด้วย Rust เป็นหลัก ถ้ายังไม่มี ไปที่ <a target="_blank" href="http://rust-lang.org">rust-lang.org</a> เเล้วทำตามขั้นนตอนการติดตั้ง <code>rustup</code> ได้เลย</p>
</li>
<li><p>ติดตั้ง Sui CLI: เมื่อ Rust พร้อมแล้ว ก็ติดตั้ง Sui Command Line Interface (CLI) จาก GitHub ของ Mysten Labs ได้เลย เปิด Terminal หรือ Command Prompt แล้วรันคำสั่งนี้:</p>
<pre><code class="lang-bash"> cargo install --locked --git https://github.com/MystenLabs/sui.git --branch testnet sui
</code></pre>
<p> ขั้นตอนนี้อาจใช้เวลาสักพักนะ เพราะมันจะดาวน์โหลดและคอมไพล์โค้ด</p>
<ul>
<li>ตรวจสอบการติดตั้ง: พิมพ์ <code>sui --version</code> ถ้าเห็นเวอร์ชัน Sui CLI แสดงว่าเรียบร้อย!</li>
</ul>
</li>
<li><p><strong>ตั้งค่า Sui Client และ Wallet</strong></p>
<ul>
<li><p>รัน <code>sui client</code> แล้วตอบ <code>y</code> เพื่อเชื่อมต่อกับ <code>Sui Fullnode Server</code> เลือก <code>Testnet</code> (หรือถ้าเคย <code>setup</code> แล้วให้รัน <code>sui client switch --env testnet</code> เพื่อเปลี่ยนไปใช้ testnet) ระบบจะสร้าง wallet address พร้อม recovery phrase อันนี้สำคัญมาก! เก็บรักษา Recovery Phrase ของคุณไว้ให้ดีๆ นะ ห้ามทำหายเด็ดขาด!</p>
</li>
<li><p>Export Private Key: สำหรับโค้ดของเรา เราจะใช้ private key เพื่อความสะดวกในการรันสคริปต์ ให้ทำตามนี้:</p>
<pre><code class="lang-bash">  <span class="hljs-comment"># ก่อนอื่น ดู address ที่ใช้งานอยู่ของคุณ</span>
  sui client active-address

  <span class="hljs-comment"># จากนั้น export private key ออกมา</span>
  sui keytool <span class="hljs-built_in">export</span> --key-identity &lt;your-address&gt;
</code></pre>
<p>  เก็บ Private key ไว้นะเราจะต้องเอาไปใส่ใน env ในขั้นตอนถัดไป</p>
</li>
<li><p>หา Testnet SUI token เพื่อจ่ายค่า gas เราจะต้องมีเหรียญ SUI สำหรับค่า Gas ในการทำธุรกรรม ไปที่ Discord ของ Sui (ค้นหา Sui Discord) แล้วเข้าไปที่ช่อง <code>#testnet-faucet</code> พิมพ์ <code>!faucet &lt;YOUR_SUI_ADDRESS&gt;</code> (แทนที่ <code>&lt;YOUR_SUI_ADDRESS&gt;</code> ด้วยที่อยู่กระเป๋านั้นเอง)</p>
</li>
<li><p>ตรวจสอบยอดคงเหลือ: รัน <code>sui client gas</code> เพื่อดูว่าได้ SUI token เข้ากระเป๋าเราแล้วรึยัง</p>
</li>
</ul>
</li>
<li><p>ติดตั้ง Node.js และ TypeScript: ถ้ายังไม่มี <code>Node.js</code> และ <code>npm</code> ก็ติดตั้งให้เรียบร้อย (แนะนำเวอร์ชันล่าสุด) จากนั้นก็สร้างโปรเจกต์ TypeScript:</p>
<p> ```bash</p>
<h1 id="heading-1-create-and-enter-directory">1. Create and enter directory</h1>
<p> mkdir walrus-onlyfans &amp;&amp; cd walrus-onlyfans</p>
<h1 id="heading-2-initialize-packagejson-with-es-module-support">2. Initialize package.json with ES module support</h1>
<p> npm init -y &amp;&amp; npm pkg set type=module</p>
<h1 id="heading-3-install-all-dependencies-in-one-command">3. Install all dependencies in one command</h1>
<p> npm install @mysten/sui @mysten/walrus dotenv
 npm install --save-dev typescript @types/node tsx</p>
</li>
</ol>
<h1 id="heading-4-initialize-typescript-config">4. Initialize TypeScript config</h1>
<p>    npx tsc --init</p>
<pre><code>
<span class="hljs-number">5.</span> ติดตั้ง VS Code (แนะนำ): ดาวน์โหลด [Visual Studio Code](https:<span class="hljs-comment">//code.visualstudio.com/) แล้วติดตั้งส่วนขยาย **Move Analyzer** เพื่อความสะดวกในการเขียนโค้ด Move ([https://docs.sui.io/references/ide/move](https://docs.sui.io/references/ide/move))</span>


เรียบร้อยถึงขั้นตอนนี้เราได้เตรียมเครื่องมือทุกอย่างเสร็จสิ้นเเล้ว พร้อมที่จะเริ่มขั้นตอนการเขียนโค๊ดละ

---

**พร้อมแล้ว มาเขียนโค้ดกัน!**

ก่อนอื่นสร้างไฟล์ <span class="hljs-string">`.env`</span> ก่อนเพื่อเก็บข้อมูลอาทิเช่น Private key ร่วมถึง Network ที่จะใช้ทดสอบ

<span class="hljs-string">``</span><span class="hljs-string">`plaintext
SUI_PRIVATE_KEY=suiprivkey1qqen0....
NETWORK=testnet</span>
</code></pre><p>สร้างไฟล์ใหม่ชื่อ <code>index.ts</code> แล้วใส่โค้ดนี้ลงไปเพื่อตั้งค่า <code>SuiClient</code> และ <code>WalrusClient</code> และเตรียม <code>keypair</code> สำหรับลงชื่อธุรกรรม</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { getFullnodeUrl, SuiClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/client"</span>;
<span class="hljs-keyword">import</span> { Ed25519Keypair } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/keypairs/ed25519"</span>;
<span class="hljs-keyword">import</span> { walrus, WalrusFile } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/walrus"</span>;
<span class="hljs-keyword">import</span> { decodeSuiPrivateKey } <span class="hljs-keyword">from</span> <span class="hljs-string">"@mysten/sui/cryptography"</span>;
<span class="hljs-keyword">import</span> { writeFileSync } <span class="hljs-keyword">from</span> <span class="hljs-string">"fs"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"dotenv/config"</span>;

<span class="hljs-keyword">const</span> PRIVATE_KEY = process.env.SUI_PRIVATE_KEY;
<span class="hljs-keyword">const</span> NETWORK = process.env.NETWORK || <span class="hljs-string">"testnet"</span>;
<span class="hljs-keyword">const</span> WALRUS_UPLOAD_RELAY = <span class="hljs-string">"https://upload-relay.testnet.walrus.space"</span>;

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// 1) Init Sui + Walrus (upload ผ่าน relay เพื่อความเสถียร)</span>
    <span class="hljs-keyword">const</span> suiClient = <span class="hljs-keyword">new</span> SuiClient({ url: getFullnodeUrl(<span class="hljs-string">"testnet"</span>) });
    <span class="hljs-keyword">const</span> walrusClient = suiClient.$extend(
        walrus({
            network: NETWORK,
            ไ: {
                host: WALRUS_UPLOAD_RELAY,
                sendTip: { max: <span class="hljs-number">1</span>_000 }, <span class="hljs-comment">// หน่วย MIST</span>
            },
        })
    );

    <span class="hljs-comment">// 2) signer</span>
    <span class="hljs-keyword">const</span> { secretKey } = decodeSuiPrivateKey(PRIVATE_KEY);
    <span class="hljs-keyword">const</span> keypair = Ed25519Keypair.fromSecretKey(secretKey);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`กำลังใช้ Address: <span class="hljs-subst">${keypair.getPublicKey().toSuiAddress()}</span>`</span>);

    <span class="hljs-comment">// 3) เตรียมเนื้อหา  </span>
    <span class="hljs-keyword">const</span> mySecretContent = <span class="hljs-string">"Test content Exclusive for onlyfans!"</span>;
    <span class="hljs-keyword">const</span> file = WalrusFile.from({
        contents: <span class="hljs-keyword">new</span> TextEncoder().encode(mySecretContent),
        identifier: <span class="hljs-string">""</span>,
    });

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"กำลังอัปโหลดไฟล์ไปที่ Walrus (ผ่าน relay)…"</span>);

    <span class="hljs-comment">// 4) เขียนไฟล์ด้วย writeFiles (รองรับ relay/metadata/retry)</span>
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> walrusClient.walrus.writeFiles({
        files: [file],
        deletable: <span class="hljs-literal">false</span>,
        epochs: <span class="hljs-number">1</span>,
        signer: keypair,
    });
    <span class="hljs-keyword">const</span> blobId = res[<span class="hljs-number">0</span>].blobId;

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`อัปโหลดไฟล์สำเร็จ! Blob ID: <span class="hljs-subst">${blobId}</span>`</span>);

    <span class="hljs-comment">// 5) รอให้ blob พร้อม แล้วอ่านข้อมูล</span>
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"\nกำลังอ่านไฟล์จาก Walrus..."</span>);
    <span class="hljs-keyword">const</span> downloadedFile = <span class="hljs-keyword">await</span> waitForBlobReady(walrusClient, blobId);

    <span class="hljs-comment">// ✅ อ่านเป็นไบต์ แล้วถอดเป็น UTF-8 เอง (ไม่มี arrayBuffer())</span>
    <span class="hljs-keyword">const</span> u8 = <span class="hljs-keyword">await</span> downloadedFile.bytes(); <span class="hljs-comment">// Uint8Array</span>
    <span class="hljs-keyword">const</span> text = <span class="hljs-keyword">new</span> TextDecoder(<span class="hljs-string">"utf-8"</span>, { fatal: <span class="hljs-literal">true</span> }).decode(u8);

    <span class="hljs-comment">// บันทึกเป็นไฟล์ข้อความ UTF-8 โดยตรง (กันอักขระเพี้ยน)</span>
    <span class="hljs-keyword">const</span> outputPath = <span class="hljs-string">"./downloaded-file.txt"</span>;
    writeFileSync(outputPath, text, { encoding: <span class="hljs-string">"utf8"</span> });
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`✅ บันทึกไฟล์ไปที่: <span class="hljs-subst">${outputPath}</span>`</span>);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`เนื้อหาไฟล์: "<span class="hljs-subst">${text}</span>"`</span>);
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">waitForBlobReady</span>(<span class="hljs-params">client: <span class="hljs-built_in">any</span>, blobId: <span class="hljs-built_in">string</span>, maxRetries = 10, delayMs = 5_000</span>) </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; maxRetries; i++) {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> [file] = <span class="hljs-keyword">await</span> client.walrus.getFiles({ ids: [blobId] });
            <span class="hljs-keyword">if</span> (file) <span class="hljs-keyword">return</span> file;
        } <span class="hljs-keyword">catch</span> {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Wait more..."</span>);
        }
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`⏳ Blob ยังไม่พร้อม (<span class="hljs-subst">${i + <span class="hljs-number">1</span>}</span>/<span class="hljs-subst">${maxRetries}</span>) รออีก <span class="hljs-subst">${delayMs / <span class="hljs-number">1000</span>}</span>s...`</span>);
        <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">r</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(r, delayMs));
    }
}

main().catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"❌ Error:"</span>, e?.message || e);
    process.exit(<span class="hljs-number">1</span>);
});
</code></pre>
<p>พร้อมแล้วมารันโค๊ดกัน</p>
<p>เปิด Terminal ในโฟลเดอร์ <code>walrus-onlyfans</code> แล้วรันคำสั่งนี้ (ก่อนรันอย่าลืมหา sui testnet กับ wal testnet -&gt; <a target="_blank" href="https://stake-wal.wal.app/?network=testnet">https://stake-wal.wal.app/?network=testnet</a> มาก่อนด้วยนะ)</p>
<pre><code class="lang-bash">npx tsx index.ts
</code></pre>
<p>เพียงเท่านี้เราก็สามารถอัพโหลดไฟล์ของเราไปเก็บไว้บน Walrus ได้เเล้วละ ซึ่งสามารดูผลลัพธ์ได้โดยการนำ Blob ID ไปค้นหาใน <a target="_blank" href="https://walruscan.com/testnet/blob/-80nLPY2ym912Vg54qhABP63izGm1yFLMy7Fg_92-uU">https://walruscan.com/testnet/blob/</a>{Blob ID} นั้นเอง ตัวอย่างเช่น</p>
<p><a target="_blank" href="https://walruscan.com/testnet/blob/-80nLPY2ym912Vg54qhABP63izGm1yFLMy7Fg_92-uU">https://walruscan.com/testnet/blob/-80nLPY2ym912Vg54qhABP63izGm1yFLMy7Fg_92-uU</a></p>
<p>หลังจากนั้นลองเช็คในไฟล์ <code>downloaded-file.txt</code> ดูนะจะได้ข้อมูลแบบนี้</p>
<pre><code class="lang-plaintext">Test content Exclusive for onlyfans!
</code></pre>
<p>อธิบายเพิ่มเติมในเเต่ละขั้นตอน</p>
<ol>
<li><p>ส่วนนี้จะเป็นการสร้าง connection ซึ่งจริงๆแล้วเราสามารถยิงไปที่ Stroage node ของ Walrus ตรงๆเลยก็ได้ แต่มักจะเกิดปัญหา เราเลยส่งไปที่ Relay แทน แต่ก็เเลกมาด้วยค่า Gas ที่ต้องจ่ายเพิ่มเล็กน้อย</p>
</li>
<li><p>ส่วนนี้ตรงไปตรงมา นั้นคือทำการ Import Private Key ของเรานั้นเอง</p>
</li>
<li><p>เป็นการเตรียมเนื้อหาเพื่อเตรียมส่งไปที่ Walrus</p>
</li>
<li><p>ทำการอัพโหลดข้อมูล ซึ่งเราจะได้ BlogId กลับมาโดยสามารถนำ BlogId ดังกล่าวไปค้นหาผ่าน explorer ได้</p>
</li>
<li><p>ส่วนนี้จะเป็นส่วนของ function ที่เขียนมาเพื่อดึงข้อมูลจาก Storage node จะเห็นว่าที่ต้องวนลูปเพราะบางครั้งเราต้องรอให้ข้อมูลพร้อมก่อน ก่อนที่จะดึงข้อมูลกลับลงมานั้นเอง</p>
</li>
</ol>
<p>ง่ายใช้ไหม ง่ายเเหละ :) นี้แค่ตัวอย่างเบื้องต้นนะ ลองเล่นกันดูก่อนนะ เดียวตอนหน้าเราจะมาดูกันว่า เเล้วถ้าเราจะเเก้ไขไฟล์ละ จะทำได้ยังไงบ้าง</p>
]]></content:encoded></item><item><title><![CDATA[EP 2: ตีบวกไอเทม ด้วย Extension Object]]></title><description><![CDATA[จากบทความที่เเล้วเราได้วิธีการสร้าง ไอเทม เเละกระเป๋าไปเรียบร้อยแล้ว หากใครยังไม่มีดาบ ไปสร้างมันมาสะบัดเดียวนี้ https://onthemoveth.hashnode.dev/ep-1-game-item-with-sui-object

มีดาบเเล้ว เทพทรูก็ต้องเข้าละ นั้นคือการตีบวกเพิ่มความสามารถให้กับไอเทมข...]]></description><link>https://onthemove.contributiondao.com/ep-2-game-item-with-sui-object</link><guid isPermaLink="true">https://onthemove.contributiondao.com/ep-2-game-item-with-sui-object</guid><category><![CDATA[Sui]]></category><category><![CDATA[onthemoveth]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Sat, 11 Oct 2025 14:16:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760192095611/474dbb10-0922-4508-8d36-2fd15b50329d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>จากบทความที่เเล้วเราได้วิธีการสร้าง ไอเทม เเละกระเป๋าไปเรียบร้อยแล้ว หากใครยังไม่มีดาบ ไปสร้างมันมาสะบัดเดียวนี้ <a target="_blank" href="https://onthemoveth.hashnode.dev/ep-1-game-item-with-sui-object">https://onthemoveth.hashnode.dev/ep-1-game-item-with-sui-object</a></p>
<hr />
<p>มีดาบเเล้ว เทพทรูก็ต้องเข้าละ นั้นคือการตีบวกเพิ่มความสามารถให้กับไอเทมของเรานั้นเอง</p>
<p><strong>ปัญหาที่ต้องแก้ก่อน</strong></p>
<p>Sui จะไม่ให้เรา เพิ่ม field ใหม่ ในโครงสร้างของไอเทมเดิมได้ตรงๆ เพราะจะทำให้ object เก่าพัง (layout ไม่ตรง) แต่ไม่ต้องกลัว! เรามีเวทมนตร์ที่ชื่อว่า Dynamic Field 🔮 ที่จะช่วยให้เพิ่มข้อมูลใหม่โดยไม่แตะ object เดิมเลย</p>
<p>พร้อมแล้วไปลุยกันเลย…</p>
<p><strong>เพิ่มพลังให้ Item ด้วย Extension</strong></p>
<p>เราจะสร้าง object ลูกชื่อ ItemExtra เพิ่มไปในไฟล์เดิมของเรา items.move โดยใช้ dynamic_field ของ Sui Framework</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> <span class="hljs-number">0x2</span>::dynamic_field <span class="hljs-keyword">as</span> df;
    public <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ExtraKey</span></span> has copy, <span class="hljs-built_in">drop</span>, store {}

    public <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ItemExtra</span></span> has key, store {
        id: UID,
        rarity: <span class="hljs-built_in">u8</span>,
        durability: <span class="hljs-built_in">u64</span>,
        parent: address,
    }

    public fun attach_extra_to_item(
        item: &amp;<span class="hljs-keyword">mut</span> Item,
        rarity: <span class="hljs-built_in">u8</span>,
        durability: <span class="hljs-built_in">u64</span>,
        ctx: &amp;<span class="hljs-keyword">mut</span> TxContext
    ) {
        <span class="hljs-keyword">let</span> extra = ItemExtra {
            id: object::new(ctx),
            rarity,
            durability,
            parent: object::id_address(item),
        };
        df::add(&amp;<span class="hljs-keyword">mut</span> item.id, ExtraKey{}, extra);
    }

    public entry fun set_extra(item: &amp;<span class="hljs-keyword">mut</span> Item, new_rarity: <span class="hljs-built_in">u8</span>, new_durability: <span class="hljs-built_in">u64</span>) {
        <span class="hljs-keyword">let</span> extra_ref = df::borrow_mut&lt;ExtraKey, ItemExtra&gt;(&amp;<span class="hljs-keyword">mut</span> item.id, ExtraKey{});
        extra_ref.rarity = new_rarity;
        extra_ref.durability = new_durability;
    }
</code></pre>
<p>ทำการอัพเกรด กันก่อนเลยเพื่อทำให้ตัว Object ของเรามีความสามารถเพิ่มเติมด้วยคำสั่งด้านล่างนี้</p>
<pre><code class="lang-bash">sui client upgrade --upgrade-capability <span class="hljs-variable">$UPGRADE_CAP_ID</span> --gas-budget 100000000
</code></pre>
<p>โดย <code>UPGRADE_CAP_ID</code> คือข้อมูล CAP_ID จากบทความที่แล้ว ที่เราได้ deploy ขึ้นไปกันนั้นเอง</p>
<p><strong>เริ่มใช้งาน Extension Object</strong></p>
<p>ทำการเพิ่มคุณสมบัติเพิ่มเติมให้กับ item ของเราก่อนโดยใช้คำสั่ง</p>
<pre><code class="lang-bash">sui client call --package <span class="hljs-variable">$PACKAGE_ID</span> --module items --<span class="hljs-keyword">function</span> attach_extra_to_item --args <span class="hljs-variable">$ITEM_ID</span> 5 1000 --gas-budget 50000000
</code></pre>
<p>หลังจากนั้นในฐานะ gm ของระบบ เราก็สามารถทำการเเก้ไขคุณสมบัติของ item ได้เต็มที่เลยโดยใช้คำสั่ง</p>
<pre><code class="lang-bash">sui client call --package <span class="hljs-variable">$PACKAGE_ID</span> --module items --<span class="hljs-keyword">function</span> set_extra --args <span class="hljs-variable">$ITEM_ID</span> 6 1200 --gas-budget 50000000
</code></pre>
<p>ซึ่งในที้นี้เราได้ทำการเเก้ไข rarity และ durability เป็น 6, 1200 ตามลำดับนั้นเอง</p>
<p><strong>Diagram แสดงความสัมพันธ์</strong></p>
<pre><code class="lang-plaintext">classDiagram
    class Inventory {
        +UID id
        +address owner
    }

    class Item {
        +UID id
        +String name
        +String class
        +u64 power
        +String image_url
    }

    class ItemExtra {
        +UID id
        +u8 rarity
        +u64 durability
        +address parent
    }

    Inventory "1" o-- "many" Item : holds
    Item "1" o-- "1" ItemExtra : dynamic_field
</code></pre>
<p>จากตรงนี้ เราก็ได้เรียนรู้วิธีการอัพเดดไอเทมเป็นที่เรียบร้อยแล้ว</p>
<p>✅ อัปเกรดแพ็กเกจโดยไม่ทำของเก่าพัง<br />✅ เพิ่ม field ใหม่ได้เรื่อย ๆ ผ่าน Extension<br />✅ ใช้ Dynamic Field ทำให้ Item ขยายได้ไม่จำกัด</p>
<p>ตอนนี้เราก็มีระบบไอเทมที่ อัปเกรดได้ จริง ๆ แล้ว จะเพิ่มระบบตีบวก คราฟต์ของ หรือแม้แต่ marketplace ก็เริ่มต่อยอดได้เลย!</p>
<p><em>By GANG | ContributionDAO</em></p>
]]></content:encoded></item><item><title><![CDATA[EP 1: Game Item with SUI Object]]></title><description><![CDATA[เปิดหัวมาแล้ว รอบนี้เตือนไว้ก่อนเลยว่าเฉพาะสาย Developer เท่านั้น !!! และแน่นอนไม่ได้จบในบทความเดียว พร้อมยัง เช็คสภาพเครื่องกันก่อน ใครยังไม่ได้ติดตั้งเครื่องมือในการพัฒนาไปดูจากที่นี้ได้นะ
https://github.com/Contribution-DAO/sui-move-intro-course-t...]]></description><link>https://onthemove.contributiondao.com/ep-1-game-item-with-sui-object</link><guid isPermaLink="true">https://onthemove.contributiondao.com/ep-1-game-item-with-sui-object</guid><category><![CDATA[Sui]]></category><category><![CDATA[onthemoveth]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Mon, 06 Oct 2025 00:10:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759669223086/0789ef80-4128-4679-8f64-a5eea34668e2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>เปิดหัวมาแล้ว <strong>รอบนี้เตือนไว้ก่อนเลยว่าเฉพาะสาย Developer เท่านั้น !!!</strong> และแน่นอนไม่ได้จบในบทความเดียว พร้อมยัง เช็คสภาพเครื่องกันก่อน ใครยังไม่ได้ติดตั้งเครื่องมือในการพัฒนาไปดูจากที่นี้ได้นะ</p>
<p><a target="_blank" href="https://github.com/Contribution-DAO/sui-move-intro-course-thai/blob/main/unit-one/lessons/1_set_up_environment.md">https://github.com/Contribution-DAO/sui-move-intro-course-thai/blob/main/unit-one/lessons/1_set_up_environment.md</a></p>
<p>พร้อมแล้วก็ไปกันเลย</p>
<p><strong>เริ่มต้นกันก่อน — ทำไม Sui Object ถึงเหมาะกับเกม?</strong></p>
<p>ในโลกของ Sui นั้นทุกๆ อย่างคือ Object ไม่ใช่แค่ token เหมือน ERC-721 แต่เป็นวัตถุที่ <strong>มีชีวิต</strong> จริง!</p>
<p>ERC-721 คือ Non-Fungible Token Standard หรือก็คือ NFT ที่เราน่าจะคุ้นๆหรือเคยมีติดมือกันมาบ้างแล้วนั้นเเหละ ซึ่งเป็นที่นิยมใช้งานกันใน EVM Smart Contract</p>
<p>มาลองจินตนาการดูว่า</p>
<ul>
<li><p>Item = ดาบในเกมของคุณ</p>
</li>
<li><p>Inventory = กระเป๋าเก็บของของผู้เล่น</p>
</li>
<li><p>Owner = ผู้เล่นคนนั้น (หรือแม้แต่ object อื่น)</p>
</li>
</ul>
<p>ซึ่ง Sui Object มีหลังพิเศษเหล่านี้:</p>
<ul>
<li><p>มี UID เป็นรหัสไม่ซ้ำ เหมือน DNA ของวัตถุ</p>
</li>
<li><p>มี Owner ที่เปลี่ยนมือได้ (หรือจะอยู่ใน object อื่นก็ยังได้!)</p>
</li>
<li><p>มี Versioning เวลามีการเปลี่ยนแปลง</p>
</li>
<li><p>มี Type Safety ด้วยภาษา Move ทำให้ bug ยากจะเกิด</p>
</li>
</ul>
<p>ถึงตรงนี้เราจะไม่เกริ่นเรื่อง Sui Object แล้วนะ ใครไม่เข้าใจมีงอล เพราะเขียนไว้เเล้วที่นี้ <a target="_blank" href="https://onthemoveth.hashnode.dev/sui-object">https://onthemoveth.hashnode.dev/sui-object</a> เพราะฉะนั้นพูดง่ายๆ คือ SUI Object คือ <strong>ไอเทมจริงๆ ที่คุณถืออยู่ในเกมส์</strong></p>
<hr />
<p><strong>โครงสร้างของเกมส์ที่เราจะสร้างในตอนนี้</strong></p>
<p>เราจะเริ่มจากพื้นฐานเเบบสุดๆกันก่อน</p>
<ul>
<li><p>🗡️ <strong>Item</strong> — NFT ไอเทม 1 ชิ้น</p>
</li>
<li><p>🎒 <strong>Inventory</strong> — กระเป๋าเก็บของของผู้เล่น</p>
</li>
</ul>
<p>จากนั้นผู้เล่นจะสามารถเสกของได้ (Mint) เเบบ GM นั้นเอง แล้วเราก็จะเอาของที่เสกขึ้นมาไปเก็บไว้ในกระเป๋า เหมือนตอนเล่นเกมส์จริงๆเลย</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759669309998/88972491-b40d-464e-b260-28a837b7cd2f.png" alt class="image--center mx-auto" /></p>
<p><strong>มาเขียนโค้ด Move กันเลย</strong></p>
<p>สร้างโปรเจ็คกันก่อน ด้วยคำสั่งด้านล่างนี้ โครงสร้างของโปรเจ็คจะถูกสร้างขึ้นมาโดยอัตโนมัติ</p>
<pre><code class="lang-bash">sui move new my_item
</code></pre>
<p>แก้ไขไฟล์ Move.toml</p>
<p>ซึ่งตรงนี้เราจะไม่อธิบายแล้วนะ เพราะมีใน <a target="_blank" href="https://github.com/Contribution-DAO/sui-move-intro-course-thai/blob/main/unit-one/lessons/2_sui_project_structure.md">https://github.com/Contribution-DAO/sui-move-intro-course-thai/blob/main/unit-one/lessons/2_sui_project_structure.md</a> แล้ว โดยเราจะเพิ่ม dependencies เข้าไป</p>
<pre><code class="lang-rust">[dependencies]
Sui = { <span class="hljs-keyword">override</span> = <span class="hljs-literal">true</span>, git = <span class="hljs-string">"https://github.com/example/example.git"</span>, subdir = <span class="hljs-string">"crates/sui-framework/packages/sui-framework"</span>,rev = <span class="hljs-string">"testnet-v1.57.2"</span> }
</code></pre>
<p>ไฟล์ sources/items.move</p>
<pre><code class="lang-rust">module my_items::items {
    <span class="hljs-keyword">use</span> <span class="hljs-number">0x2</span>::object::{<span class="hljs-keyword">Self</span>, UID};
    <span class="hljs-keyword">use</span> <span class="hljs-number">0x2</span>::tx_context::{<span class="hljs-keyword">Self</span> <span class="hljs-keyword">as</span> tx, TxContext};
    <span class="hljs-keyword">use</span> <span class="hljs-number">0x2</span>::transfer;
    <span class="hljs-keyword">use</span> std::string::<span class="hljs-built_in">String</span>;

    <span class="hljs-comment">// Item: ดาบสุดแกร่งของเรา</span>
    public <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Item</span></span> has key, store {
        id: UID,
        name: <span class="hljs-built_in">String</span>,
        class: <span class="hljs-built_in">String</span>,
        power: <span class="hljs-built_in">u64</span>,
        image_url: <span class="hljs-built_in">String</span>,
    }

    <span class="hljs-comment">// Inventory: กระเป๋าผู้เล่น</span>
    public <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Inventory</span></span> has key, store {
        id: UID,
        owner: address,
    }

    <span class="hljs-comment">// Mint ไอเทมใหม่ให้ผู้เล่น</span>
    public fun mint_to_sender(
        name: <span class="hljs-built_in">String</span>,
        class: <span class="hljs-built_in">String</span>,
        power: <span class="hljs-built_in">u64</span>,
        image_url: <span class="hljs-built_in">String</span>,
        ctx: &amp;<span class="hljs-keyword">mut</span> TxContext
    ) {
        <span class="hljs-keyword">let</span> item = Item { id: object::new(ctx), name, class, power, image_url };
        transfer::public_transfer(item, tx::sender(ctx));
    }

    <span class="hljs-comment">// สร้างกระเป๋าใหม่</span>
    public fun create_inventory(ctx: &amp;<span class="hljs-keyword">mut</span> TxContext) {
        <span class="hljs-keyword">let</span> inv = Inventory { id: object::new(ctx), owner: tx::sender(ctx) };
        transfer::public_transfer(inv, tx::sender(ctx));
    }

    <span class="hljs-comment">// เอาไอเทมใส่กระเป๋า</span>
    public fun put_into_inventory(inv: &amp;Inventory, item: Item) {
        <span class="hljs-keyword">let</span> inv_addr = inv.owner;
        transfer::transfer(item, inv_addr);
    }

    <span class="hljs-comment">// เอาไอเทมออกจากกระเป๋า</span>
    public fun take_out_from_inventory_to_sender(
        inv: &amp;Inventory, item: Item, ctx: &amp;<span class="hljs-keyword">mut</span> TxContext
    ) {
        <span class="hljs-built_in">assert!</span>(inv.owner == tx::sender(ctx), <span class="hljs-number">1</span>);
        transfer::public_transfer(item, tx::sender(ctx));
    }
}
</code></pre>
<p>Code พร้อม มาเริ่ม Deploy กัน</p>
<ol>
<li>เปลี่ยน environment</li>
</ol>
<pre><code class="lang-bash">sui client switch --env testnet
</code></pre>
<ol start="2">
<li>ขอเหรียญจาก faucet</li>
</ol>
<pre><code class="lang-bash">sui client faucet
</code></pre>
<ol start="3">
<li>Build และ Publish</li>
</ol>
<pre><code class="lang-bash">sui move build
sui client publish --gas-budget 100000000
</code></pre>
<p>หลังจาก publish สำเร็จเราก็ได้</p>
<ul>
<li><p>packageId (เหมือน ID ของโปรเจกต์)</p>
</li>
<li><p>UpgradeCap (กุญแจไว้ใช้ตอนอัปเกรดใน EP2)</p>
</li>
</ul>
<p>ลองเอา packageId ไปเปิดบน Testnet Explorer ดูนะ</p>
<pre><code class="lang-plaintext">https://suiscan.xyz/testnet/object/{packageId}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759708763888/a83b53c8-ea04-4753-89de-6cd0e8a72f38.png" alt class="image--center mx-auto" /></p>
<p><strong>ทดลองใช้งานจริง</strong></p>
<p>สร้างกระเป๋าเอาไว้เก็บ Item (Inventory)</p>
<pre><code class="lang-bash">sui client call \
  --package <span class="hljs-variable">$PACKAGE_ID</span> \
  --module items \
  --<span class="hljs-keyword">function</span> create_inventory \
  --gas-budget 50000000
</code></pre>
<p>Mint ดาบในตำนาน</p>
<pre><code class="lang-bash">sui client call \
  --package <span class="hljs-variable">$PACKAGE_ID</span> \
  --module items \
  --<span class="hljs-keyword">function</span> mint_to_sender \
  --args <span class="hljs-string">"Excalibur"</span> <span class="hljs-string">"Weapon"</span> 9001 <span class="hljs-string">"ipfs://bafy..."</span> \
  --gas-budget 50000000
</code></pre>
<p>เก็บดาบใส่กระเป๋าให้เรียบร้อย</p>
<pre><code class="lang-bash">sui client call \
  --package <span class="hljs-variable">$PACKAGE_ID</span> \
  --module items \
  --<span class="hljs-keyword">function</span> put_into_inventory \
  --args <span class="hljs-variable">$INVENTORY_ID</span> <span class="hljs-variable">$ITEM_ID</span> \
  --gas-budget 50000000
</code></pre>
<p>และนี้คือตัวอย่าง transaction สุดท้ายของเรา</p>
<p><a target="_blank" href="https://suiscan.xyz/testnet/object/0xb020131b5c1004b42a8f0d6ccf9f9c23b0f711ff14199538874fc0219b9f37d7/tx-blocks">https://suiscan.xyz/testnet/object/0xb020131b5c1004b42a8f0d6ccf9f9c23b0f711ff14199538874fc0219b9f37d7/tx-blocks</a></p>
<p>ดาบถูกจัดเก็บลงในกระเป๋าละ</p>
<p><strong>Diagram แสดงความสัมพันธ์</strong></p>
<pre><code class="lang-plaintext">classDiagram
    class Inventory {
        +UID id
        +address owner
    }

    class Item {
        +UID id
        +String name
        +String class
        +u64 power
        +String image_url
    }

 Inventory "1" o-- "many" Item : holds
</code></pre>
<p>ตอนนี้เราได้สร้างระบบไอเทมในเกมส์ครบแล้ว<br />✅ สร้างได้<br />✅ เก็บในกระเป๋าได้<br />✅ โอนไปมาได้</p>
<p>แต่ถ้าอยากตีบวกดาบให้เทพขึ้นล่ะ? หรือถ้าอยากเพิ่ม rarity, durability? จะทำยังไง… ลองไปปรับเพิ่มกันดูนะครับ ก่อนที่เราจะมาเฉลยใน EP.2 :) เจอกัน</p>
<p><em>By GANG | ContributionDAO</em></p>
]]></content:encoded></item><item><title><![CDATA[Talus โครงสร้างพื้นฐานสำหรับ AI Agents บน Blockchain]]></title><description><![CDATA[ปี 2025 แล้ว มีใครยังไม่ได้ใช้งาน AI บ้าง เราเชื่อว่าทุกคนในที่นี้ น่าจะผ่านการใช้ AI มาระดับนึง ไม่ว่าจะผ่าน ChatGPT หรือ AI Agent ต่างๆ โดยเฉพาะสาย Software Developer รู้นะ ว่าใช้มากกว่า 2-3 ตัวแน่นอน :)
AI กำลังเปลี่ยนโลกอย่างรวดเร็ว เราคงเห็น AI ...]]></description><link>https://onthemove.contributiondao.com/talus-ai-agents-blockchain</link><guid isPermaLink="true">https://onthemove.contributiondao.com/talus-ai-agents-blockchain</guid><category><![CDATA[talus]]></category><category><![CDATA[onthemoveth]]></category><category><![CDATA[AI]]></category><category><![CDATA[Sui]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Sun, 05 Oct 2025 09:29:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759655866429/cde87f5b-ca0c-4af5-93ce-48485b789c4f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>ปี 2025 แล้ว มีใครยังไม่ได้ใช้งาน AI บ้าง เราเชื่อว่าทุกคนในที่นี้ น่าจะผ่านการใช้ AI มาระดับนึง ไม่ว่าจะผ่าน ChatGPT หรือ AI Agent ต่างๆ โดยเฉพาะสาย Software Developer รู้นะ ว่าใช้มากกว่า 2-3 ตัวแน่นอน :)</p>
<p>AI กำลังเปลี่ยนโลกอย่างรวดเร็ว เราคงเห็น AI Agents ที่สามารถทำงานแทนมนุษย์ได้ เช่น ตอบแชทอัตโนมัติ วิเคราะห์ข้อมูล หรือแม้แต่ตัดสินใจลงทุน แต่ปัญหาคือ Agent เหล่านี้ส่วนใหญ่ยังทำงานบน Web2 infrastructure นั้นหมายถึงรันอยู่บน เซิฟเวิร์ค ของบริษัท ขาดความโปร่งใส และไม่สามารถตรวจสอบได้ว่าเบื้องหลังเกิดอะไรขึ้น นั้นเเหละถึงเวลาของ Blockchain แล้ว</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759653351017/90dbd0b5-e2ff-402d-ad69-ed2693390542.png" alt class="image--center mx-auto" /></p>
<p>Talus จึงเกิดขึ้นมาเพื่อแก้โจทย์นี้: เปลี่ยน AI Agents ให้ทำงานได้บนระบบของ Blockchain ซึ่งจะทำงานอัตโนมัติ, โปร่งใส และมีเศรษฐกิจของตัวเอง (marketplace + tokenomics)</p>
<p><strong>ทำไมต้อง Talus?</strong></p>
<p>อย่างที่รู้กันแล้วว่า Blockchain ปัจจุบันมี Smart contracts ที่ทำงานตาม logic ที่ถูกเขียนไว้ล่วงหน้า ซึ่งทุกอย่างจะถูกกำหนดไว้หมดแล้ว ถ้าหากสถานการณ์ที่ต้องมีการปรับเปลี่ยนการตัดสินใจตามสถานการณ์ละ? นั้นคือสิ่งที่ Talus มี</p>
<ul>
<li><p>สมอง (Reasoning): ความสามารถในการตัดสินใจเชิงซับซ้อน ปรับตัวตามสถานการณ์จริง</p>
</li>
<li><p>มือ (Action): ความสามารถในการทำงานอัตโนมัติ 24/7 โดยไม่ต้องให้ใครมากดสั่งการ</p>
</li>
</ul>
<p><strong>ปัญหาที่ Talus อยากแก้</strong></p>
<ul>
<li><p>AI Agents ทั่วไปมักรันในระบบแบบ Centralized — ขึ้นกับ Server, API, และโครงสร้างพื้นฐานที่ถูกควบคุมโดยหน่วยงานกลาง</p>
</li>
<li><p>ถ้า AI Agents ทำธุรกรรม มีทรัพย์สิน จัดการเงิน — ต้องมีความโปร่งใส ตรวจสอบได้ และมีตัวตนที่ชัดเจน</p>
</li>
<li><p>ต้องการให้ AI Agents สามารถ คิด → ตัดสินใจ → ดำเนินการ ได้อย่างอิสระ พร้อมทำงานร่วมกับระบบอื่น ๆ ได้</p>
</li>
</ul>
<p>เพราะฉะนั้นเเล้วด้วยความสามารถของ Talus จะทำให้เกิดผลิตภัณฑ์ใหม่ที่หลากหลายเช่น</p>
<ul>
<li><p>DAO ที่ปรับตัวอัตโนมัติ วิเคราะห์ proposal และโหวตแทน community</p>
</li>
<li><p>DeFi ที่เรียนรู้และเปลี่ยนกลยุทธ์ตามตลาด จัดการ yield farming, rebalancing อัตโนมัติ</p>
</li>
<li><p>AI trader ที่ทำงานตลอดเวลาโดยไม่ต้องมีเจ้าของมา log in</p>
</li>
<li><p>Agent-as-a-Service (AaaS) — dApp สามารถเรียกใช้ Agent บริการเฉพาะด้านในแอปพลิเคชั่นได้</p>
</li>
</ul>
<p>เริ่มน่าสนใจขึ้นบ้างแล้วใช่ไหม มาดูกันว่า มาดูโครงสร้างหลักๆ ของ Talus มีอะไรกันบ้าง เอาแบบใหญ่คงเเบ่งออกมาได้ประมาณ 4 ส่วนละ</p>
<p><strong>โครงสร้างหลักของ Talus</strong></p>
<ol>
<li>Talus Agentic Framework (TAF)</li>
</ol>
<p>คือ หัวใจหลักของ Talus ที่นิยามวิธีการทำงานของ Agent ทั้งหมดใน Talus — เป็นมาตรฐานกลางสำหรับจัดการ computation, การส่งข้อมูล, และการจ่ายเงิน</p>
<p>TAF ประกอบด้วยสององค์ประกอบย่อยหลัก:</p>
<ul>
<li><p>Talus Tools: บริการย่อย (onchain หรือ offchain) เช่น oracle, LLM API, smart contract logic ฯลฯ</p>
</li>
<li><p>Talus Workflows: กราฟลำดับการทำงาน (DAG) ที่เชื่อม Talus Tools เข้าด้วยกันให้เกิด workflow อัตโนมัติ เช่น oracle → AI → swap</p>
</li>
</ul>
<ol start="2">
<li>Nexus Protocol</li>
</ol>
<p>Nexus ใน Talus ทำหน้าที่เป็น Execution layer สำหรับรัน workflow และตรวจสอบผลลัพธ์แบบ verifiable</p>
<p>Nexus มีสองส่วนหลัก:</p>
<ul>
<li><p>Onchain Components (Nexus Onchain Packages หรือย่อว่าๆ NOP):</p>
<ul>
<li><p>ชุด Smart contract ที่นิยาม Talus Tools, Workflows, และ registry</p>
</li>
<li><p>พัฒนาโดยทีม Talus Labs และแน่นอน deploy บน Sui Network โดยใช้ภาษา Move</p>
</li>
<li><p>นักพัฒนาภายนอกสามารถสร้าง Tool Packages และ Agent Packages (TAPs) ที่เชื่อมกับ NOP เพื่อกำหนด agent ของตัวเองได้</p>
</li>
</ul>
</li>
<li><p>Offchain Components (Leader Network):</p>
<ul>
<li><p>ระบบตัวกลางที่คอยฟัง event จาก onchain และรัน tools/offchain API ตามลำดับใน workflow</p>
</li>
<li><p>ทำหน้าที่เหมือน oracle ที่ปลอดภัย มีระบบ proof, timer, และการจัดการ payment อัตโนมัติ</p>
</li>
</ul>
</li>
</ul>
<p>นั้นหมายถึง Nexus เป็นโครงสร้างที่ทำให้ agent สามารถ คิด–ตัดสินใจ–และลงมือทำ ได้อย่างอิสระ โดยไม่ต้องมีคนมาสั่งนั้นเอง</p>
<ol start="3">
<li>Economic Layers</li>
</ol>
<p>Talus สร้างระบบเศรษฐกิจ 3 ชั้น เพื่อให้ developer และผู้ใช้มีแรงจูงใจร่วมกัน</p>
<ul>
<li><p>Tool Marketplace:<br />  นักพัฒนาขาย Talus Tools ของตนเอง (เช่น oracle, AI model) และรับค่าธรรมเนียมทุกครั้งที่ถูกเรียกใช้</p>
</li>
<li><p>Agent Marketplace:<br />  ผู้ใช้สามารถเลือกหรือปรับแต่ง AI Agents ที่ทำงานอัตโนมัติใน use case ต่าง ๆ เช่น trading, prediction, automation</p>
</li>
<li><p>Agent-as-a-Service:<br />  dApps ภายนอกสามารถเรียกใช้ agent ของ Talus เป็น service ของตนเอง (เหมือน API-as-a-Service แต่เป็น decentralized)</p>
</li>
</ul>
<p>ทุกการใช้งานใช้โทเค็น $US เป็นสื่อกลางการจ่ายเงิน และ Talus จะมีระบบ staking/slashing เพื่อรับประกันความถูกต้องของบริการ</p>
<ol start="4">
<li><p>Talus Network — Coordination &amp; Infrastructure Layer</p>
<p> เป็นส่วนที่จะประสานทั้งหมดเข้าด้วยกัน:</p>
</li>
</ol>
<ul>
<li><p>ทำหน้าที่จัดการ การประสานงาน, การส่งข้อมูล, การจ่ายเงิน, และ การตรวจสอบผลลัพธ์</p>
</li>
<li><p>มีระบบ asynchronous workflow engine เพื่อให้ workflow ทำงานต่อเนื่องโดยไม่ต้องรอการตอบกลับแบบ synchronous ของบล็อกเชน</p>
</li>
<li><p>รองรับการเชื่อมต่อกับ AI Models, TEE, และ Cross-chain system เพื่อให้ agent สามารถทำงานได้ในหลายระบบพร้อมกัน</p>
</li>
</ul>
<p>อธิบายมาก็เยอะแล้ว ข้อมูลทุกอย่างสามารถดูได้จากใน <a target="_blank" href="https://docs.talus.network/">Docs</a> และ <a target="_blank" href="https://docs.talus.network/talus-overview/writings">White paper</a> แต่นั้นเเหละข้อมูลเยอะเกิ๊น ข้ามมันไปก่อน มาดูดีกว่าว่าตอนนีเราทำอะไรกับมันได้บ้าง</p>
<p>สำหรับคนที่สนใจสามารถเข้าร่วมทดสอบ Testnet ได้แล้ววันนี้ <a target="_blank" href="https://testnet.talus.network/testnet">https://testnet.talus.network/testnet</a> ตัว Talus เองก็พึ่งระดมทุนมาได้ด้วยถึง $10M (<a target="_blank" href="https://x.com/Talus_Labs/status/1972662568213987756">https://x.com/Talus_Labs/status/1972662568213987756</a>) มี incentive ให้ด้วยนะ ลองดูได้จากที่นี้ <a target="_blank" href="https://x.com/Talus_Labs/status/1973014471246327819">https://x.com/Talus_Labs/status/1973014471246327819</a></p>
<p>ส่วนสาย Builder เองตอนนี้ก็มี SDK ออกมาแล้วครับ รวมถึงตัวอย่างการใช้งานด้วย ลองดูได้ที้นี้นะ</p>
<ul>
<li><p><a target="_blank" href="https://docs.talus.network/getting-started/math-branching-quickstart">https://docs.talus.network/getting-started/math-branching-quickstart</a></p>
</li>
<li><p><a target="_blank" href="https://docs.talus.network/developer-docs/index">https://docs.talus.network/developer-docs/index</a></p>
</li>
</ul>
<p>วันนี้เกริ่นกันแค่นี้ก่อน สารภาพว่าแค่อ่าน Docs ก็หมดแรงแล้ว…</p>
]]></content:encoded></item><item><title><![CDATA[SUI Object]]></title><description><![CDATA[วันนี้เรามาเริ่มขยับให้ลึกขึ้นไปอีกนิดนึง ซึ่งเรื่องนี้เป็นเรื่องสำคัญต่อนักพัฒนา dApp บน SUI network นั้นก็คือ Object นั้นเอง นี้คือเรื่องพื้นฐานที่สำคัญเลยเเหละ มาเริ่มกันเลยดีกว่า
ในโลกการเขียนโปรเเกรม Web 2.0 ที่เราคุ้นเคย ถ้าเราเป็น Web 2.0 Deve...]]></description><link>https://onthemove.contributiondao.com/sui-object</link><guid isPermaLink="true">https://onthemove.contributiondao.com/sui-object</guid><category><![CDATA[sui object]]></category><category><![CDATA[Sui]]></category><category><![CDATA[onthemoveth]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Mon, 22 Sep 2025 08:43:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758531342598/3a7b9e40-3f1f-4fb5-8fc4-c66785e69cb7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>วันนี้เรามาเริ่มขยับให้ลึกขึ้นไปอีกนิดนึง ซึ่งเรื่องนี้เป็นเรื่องสำคัญต่อนักพัฒนา dApp บน SUI network นั้นก็คือ Object นั้นเอง นี้คือเรื่องพื้นฐานที่สำคัญเลยเเหละ มาเริ่มกันเลยดีกว่า</p>
<p>ในโลกการเขียนโปรเเกรม Web 2.0 ที่เราคุ้นเคย ถ้าเราเป็น Web 2.0 Developer เวลาเก็บข้อมูลของผู้ใช้หรือระบบ เรามักจะเก็บเป็น Row ใน Database (เช่น PostgreSQL, MySQL, MongoDB)</p>
<ul>
<li><p>มี user_id เป็น Primary Key</p>
</li>
<li><p>เก็บข้อมูลเช่น username, email, balance</p>
</li>
<li><p>เวลาแก้ไข เราแค่รัน UPDATE หรือ DELETE ตามสิทธิ์ที่ระบบกำหนด</p>
</li>
</ul>
<p>คุณคือเจ้าของระบบและ Database ถ้าอยาก rollback หรือ migrate schema ก็ทำได้เองหมด</p>
<p>แต่เดียวก่อน ในจักรวาลของ SUI Network นั้น ทุกอย่างไม่ได้เก็บเป็นแค่ Row/Column แบบที่เราคุ้นเคยกัน แต่เก็บทั้งหมดอยู่ในรูปแบบ Object</p>
<ul>
<li><p>Object = Data + Owner + Version</p>
</li>
<li><p>ทุกครั้งที่เปลี่ยนแปลง Object จะไม่ใช่การเขียนทับ แต่คือการสร้าง เวอร์ชันใหม่ของสถานะ (state) เพื่อรักษาความถูกต้องและป้องกัน conflict</p>
</li>
<li><p>เจ้าของ Object (owner) มีสิทธิ์ควบคุมว่าจะโอน ยืมใช้ หรือแก้ไขได้หรือไม่</p>
</li>
</ul>
<p><strong>SUI Object คือ</strong></p>
<p>หน่วยเก็บข้อมูล (data unit) พื้นฐานที่สุดที่ใช้แทนทุกสิ่ง ไม่ว่าจะเป็นโทเค็น, NFT, smart contract state หรือแม้กระทั่งข้อมูลทั่วไป ต่างจาก blockchain อื่นๆ (ที่เก็บข้อมูลในรูปแบบ account + balance หรือ key-value store) Sui ใช้ Object-Centric Model ทำให้แต่ละ Object มีตัวตนแยกชัดเจน (unique identity) และสิทธิ์การเป็นเจ้าของในตัวเอง</p>
<p><strong>คุณสมบัติคร่าวๆของ SUI Object จะมีดังนี้</strong></p>
<ul>
<li><p>มี ID ที่ไม่ซ้ำกัน (Object ID)</p>
<p>  ทุก Object จะมีรหัสเฉพาะ (32 bytes) เพื่อระบุว่าเป็น Object ไหน</p>
</li>
<li><p>มีเจ้าของ (Ownership)</p>
<p>  Object จะกำหนดว่าใครเป็นเจ้าของหรือสามารถเข้าถึงได้ โดย ownership มีหลายรูปแบบ เช่น</p>
<ul>
<li><p>Address-owned → เป็นของ address ใด address หนึ่ง นั้นคือมีเจ้าของชัดเจนนั้นเอง ใช้กับ NFT, Token, Item</p>
</li>
<li><p>Shared → ใช้ร่วมกันหลายคน เช่น DEX Pool, Counter</p>
</li>
<li><p>Immutable → อ่านได้อย่างเดียว เปลี่ยนแปลงไม่ได้ ใช้กับพวก Metadata หรือ Config</p>
</li>
<li><p>Wrapped → ถูกเก็บซ้อนอยู่ใน Object อื่น</p>
</li>
<li><p>Party → ใช้ร่วมกับ party ของผู้ที่เกี่ยวข้อง</p>
</li>
</ul>
</li>
</ul>
<p><strong>เปรียบเทียบกับ Database</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Concept (Web2)</td><td>Database (SQL/NoSQL)</td><td>Sui Blockchain (Web3)</td></tr>
</thead>
<tbody>
<tr>
<td>Primary Key</td><td>user_id (int/uuid)</td><td>Object ID (Global Unique)</td></tr>
<tr>
<td>Data</td><td>Row / Document</td><td>Object Fields (struct)</td></tr>
<tr>
<td>Permission</td><td>App Logic (ACL/Role)</td><td>Owner field ใน Object</td></tr>
<tr>
<td>Update</td><td>UPDATE row ...</td><td>สร้าง Object เวอร์ชันใหม่</td></tr>
<tr>
<td>Delete</td><td>DELETE row ...</td><td>Object ถูกทำลาย (destroy) ด้วยฟังก์ชันใน Move ถ้า logic อนุญาต</td></tr>
</tbody>
</table>
</div><p>มาดูตัวอย่างกันดีกว่า</p>
<p>Web 2.0 (SQL)</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> players (<span class="hljs-keyword">id</span>, <span class="hljs-keyword">name</span>, score) <span class="hljs-keyword">VALUES</span> (<span class="hljs-number">1</span>, <span class="hljs-string">'Alice'</span>, <span class="hljs-number">10</span>); 
<span class="hljs-keyword">UPDATE</span> players <span class="hljs-keyword">SET</span> score = <span class="hljs-number">20</span> <span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">id</span> = <span class="hljs-number">1</span>;
</code></pre>
<p>SUI (Move)</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Player</span></span> has key {
    id: UID,
    name: <span class="hljs-built_in">String</span>,
    score: <span class="hljs-built_in">u64</span>,
}

public entry fun new_player(name: <span class="hljs-built_in">String</span>, ctx: &amp;<span class="hljs-keyword">mut</span> TxContext) {
    <span class="hljs-keyword">let</span> player = Player {
        id: object::new(ctx),
        name,
        score: <span class="hljs-number">0</span>,
    };
    transfer::transfer(player, tx_context::sender(ctx));
}

public entry fun update_score(player: &amp;<span class="hljs-keyword">mut</span> Player, new_score: <span class="hljs-built_in">u64</span>) {
    player.score = new_score;
}
</code></pre>
<p><strong>การ Upgrade ของ Object</strong></p>
<p>อีกสิ่งนึงที่น่าสนใจคือกรณีที่เราต้องการเเก้ไขข้อมูลของ SUI Object เพราะจริง ๆ แล้วผูกกับ แนวคิด Versioning ของ Object โดยตรง เพราะทุกครั้งที่มีการแก้ไข (mutate) หรือโอน (transfer) ตัว Object จะไม่ใช่การเขียนทับ แต่คือการสร้าง Version ใหม่ ของ Object นั้น</p>
<ul>
<li><p>Object Versioning</p>
<ul>
<li><p>ทุกการเปลี่ยนแปลงของ Object = การสร้าง Version ใหม่</p>
</li>
<li><p>ระบบจะเก็บคู่ <code>ObjectID + Version</code> เพื่ออ้างอิงสถานะปัจจุบัน</p>
</li>
<li><p>ถ้า Transaction ใช้ Object version เก่า → จะล้มเหลวทันที (ป้องกัน conflict)</p>
</li>
</ul>
</li>
<li><p>Module Upgrade</p>
<ul>
<li><p>Logic หรือฟังก์ชันการทำงานที่ผูกกับ Object อยู่ใน Move Package</p>
</li>
<li><p>เมื่อจะอัปเกรด logic → ไม่ได้แก้ package เดิม แต่ใช้การ Deploy Package ใหม่</p>
</li>
<li><p>กลไกนี้ช่วยให้ blockchain ยังคงรักษาความเป็น Immutable ของ code เดิมได้</p>
</li>
</ul>
</li>
<li><p>Migration Script</p>
<ul>
<li><p>ใช้เพื่อ ย้ายข้อมูลจาก Object เดิมไปยัง Object ใหม่ ที่สร้างด้วย logic เวอร์ชันล่าสุด</p>
</li>
<li><p>เหมาะในกรณีที่โครงสร้างข้อมูล (struct) หรือ logic เปลี่ยนไปจนไม่สามารถใช้ Object เดิมได้</p>
</li>
<li><p>เป็นขั้นตอนสำคัญในการรักษาความต่อเนื่องของ state และ user experience</p>
</li>
</ul>
</li>
</ul>
<p><strong>ทำไม Sui Object สำคัญ?</strong></p>
<p>Sui Object เป็นหัวใจของการออกแบบ Sui blockchain เพราะมันทำให้เครือข่าย เร็วขึ้น, ปลอดภัยขึ้น, ยืดหยุ่นกว่า และพัฒนาได้ง่ายกว่า blockchain แบบ account-based เดิม ๆ โดยสามารถสรุปได้เป็น 4 แกนหลักดังนี้</p>
<ul>
<li><p>Scalable (รองรับการขยายตัวสูง)</p>
<ul>
<li><p>เพราะ Object แยกออกจากกัน → ธุรกรรมที่แตะคนละ Object สามารถประมวลผล พร้อมกัน (parallel execution) ได้</p>
</li>
<li><p>ต่างจาก Ethereum ที่ต้องประมวลผลแบบ sequential ทีละธุรกรรม</p>
</li>
<li><p>ทำให้ Sui รองรับ throughput สูงและ latency ต่ำ เหมาะกับ dApp ขนาดใหญ่ เช่น DeFi และ GameFi</p>
</li>
</ul>
</li>
<li><p>Security (ปลอดภัยด้วยสิทธิการเข้าถึง)</p>
<ul>
<li><p>ทุก Object มี owner ที่ชัดเจน (address, shared, immutable, party ฯลฯ)</p>
</li>
<li><p>สิทธิ์การเข้าถึงถูกควบคุมอย่างเข้มงวด → ป้องกันการใช้ object โดยไม่ได้รับอนุญาต</p>
</li>
<li><p>ระบบ versioning ทำให้มั่นใจได้ว่าธุรกรรมใช้ object เวอร์ชันล่าสุด ลดปัญหา conflict หรือ double spend</p>
</li>
</ul>
</li>
<li><p>Programmable Asset (ทรัพย์สินที่โปรแกรมได้)</p>
<ul>
<li><p>บน Sui, Object ไม่ได้เป็นแค่ data แต่เป็น asset ที่มี logic ของตัวเอง ตัวอย่าง: Sword ในเกมไม่ใช่แค่ metadata ของ NFT แต่สามารถมี logic เช่น upgrade_sword, transfer, หรือ equip อยู่ภายใน</p>
</li>
<li><p>เปิดโอกาสให้สร้างแอปที่ซับซ้อนขึ้นได้ โดยใช้ Object เป็น building block</p>
</li>
</ul>
</li>
<li><p>Upgradable (อัปเกรดได้ต่อเนื่อง)</p>
<ul>
<li><p>Object Versioning → ทุกครั้งที่แก้ไข object จะสร้าง version ใหม่</p>
</li>
<li><p>Module Upgrade → logic เปลี่ยนได้ด้วยการ deploy package ใหม่</p>
</li>
<li><p>Migration Script → ย้ายข้อมูลจาก object เดิมไป object ใหม่ได้</p>
</li>
<li><p>ทำให้ทั้ง data และ logic สามารถพัฒนาและอัปเกรดได้โดยไม่สูญเสีย state เก่า</p>
</li>
</ul>
</li>
</ul>
<p>ยาวหน่อยนะ เเต่ SUI Object เป็นพื้นฐานที่ต้องเข้าใจจริงๆ ก่อนที่จะเริ่มพัฒนา dApp บน SUI Network ซึ่งจากข้อมูลข้างบนจะเริ่มมีเเตะๆโค๊ดบ้างแล้วเเหละ สำหรับใครที่อยากจะเริ่มสามารถดูคอร์ดเรียนเบื้องต้นภาษาไทยได้จากที่นี้นะ</p>
<p><a target="_blank" href="https://github.com/Contribution-DAO/sui-move-intro-course-thai">https://github.com/Contribution-DAO/sui-move-intro-course-thai</a></p>
<p>โดยในบทเรียนที่ 1 จะสอนเกี่ยวกับการติดตั้งค่า เเละบทเรียนที่ 2 จะสอนเกี่ยวกับ SUI Object นั้นเอง</p>
<hr />
<p>ของเเถม จริงๆรายละเอียดเบื้องต้นเกี่ยวกับ SUI Object จบไปละ เเต่เนื้อหาข้างล่างเเถมให้</p>
<p><strong>เปรียบเทียบกับ EVM chain</strong></p>
<p>มาดูคำนิยามกันก่อนว่า EVM คืออะไร</p>
<ul>
<li><p>EVM (Ethereum Virtual Machine)</p>
<p>  Smart Contract ที่ deploy แล้ว immutable (เปลี่ยนไม่ได้) ถ้าอยาก upgrade ต้องใช้ Proxy Pattern หรือ deploy contract ใหม่</p>
</li>
<li><p>Sui Move</p>
<p>  Package (smart contract logic) ถูก deploy ได้ทั้งแบบ immutable และ upgradable และ Object ที่ถูกสร้างจาก package เก่า สามารถ migrate ไปใช้ logic ใหม่ได้</p>
</li>
</ul>
<p>ตัวอย่าง EVM ก็เช่น Ethereum หรือ Layer 2 network อาทิเช่น Arbitrum Optimism เป็นต้น</p>
<p>ตารางเปรียบเทียบมัดต่อมัด</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>เรื่อง</td><td>EVM (Ethereum, Layer 2 network etc.)</td><td>Sui</td></tr>
</thead>
<tbody>
<tr>
<td>Default Contract</td><td>Immutable (แก้ไขไม่ได้หลัง deploy)</td><td>Immutable หรือ Upgradable (เลือกได้)</td></tr>
<tr>
<td>การเเก้ไข Logic</td><td>ใช้ Proxy Pattern เช่น Transparent Proxy, UUPS (delegate call ไป logic ใหม่)</td><td>Publish Package เวอร์ชันใหม่ (upgradeable package)</td></tr>
<tr>
<td>การเก็บข้อมูล</td><td>เก็บใน Storage Slot ของ Proxy</td><td>เก็บใน Object (มี owner, version)</td></tr>
<tr>
<td>การเเก้ไขข้อมูล</td><td>ต้องเขียน Migration Function ใน logic ใหม่</td><td>ใช้ Migration Script: โอนค่าจาก Object เก่า → Object ใหม่</td></tr>
<tr>
<td>Ownership ของ Logic</td><td>Admin Proxy สามารถเปลี่ยน Implementation</td><td>Upgrade Cap สามารถกำหนดสิทธิ์ว่าใคร upgrade package ได้</td></tr>
<tr>
<td>ความซับซ้อน</td><td>Proxy ต้อง handle storage layout compatibility</td><td>Sui handle object versioning โดยตรง, migration explicit</td></tr>
<tr>
<td>ความเสี่ยง</td><td>Storage collision, delegatecall bug</td><td>ต้องระวังการ migrate object ให้ถูกต้อง</td></tr>
</tbody>
</table>
</div><p>ตัวอย่างการอัพเดต</p>
<p>EVM (UUPS Proxy)</p>
<pre><code class="lang-solidity"><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Proxy</span> </span>{
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> implementation;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">upgrade</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> newImpl</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyAdmin</span> </span>{
        implementation <span class="hljs-operator">=</span> newImpl;
    }

    <span class="hljs-function"><span class="hljs-keyword">fallback</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        (<span class="hljs-keyword">bool</span> success, ) <span class="hljs-operator">=</span> implementation.<span class="hljs-built_in">delegatecall</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">data</span>);
        <span class="hljs-built_in">require</span>(success);
    }
}
</code></pre>
<p>Sui (Upgrade Package + Migration)</p>
<pre><code class="lang-rust"><span class="hljs-comment">// Package V1</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Player</span></span> has key {
    id: UID,
    name: <span class="hljs-built_in">String</span>,
    score: <span class="hljs-built_in">u64</span>,
}

<span class="hljs-comment">// Package V2 (เพิ่ม avatar_url)</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">PlayerV2</span></span> has key {
    id: UID,
    name: <span class="hljs-built_in">String</span>,
    score: <span class="hljs-built_in">u64</span>,
    avatar_url: <span class="hljs-built_in">String</span>,
}

public entry fun migrate(old: Player, ctx: &amp;<span class="hljs-keyword">mut</span> TxContext) {
    <span class="hljs-keyword">let</span> new_player = PlayerV2 {
        id: object::new(ctx),
        name: old.name,
        score: old.score,
        avatar_url: <span class="hljs-built_in">String</span>::utf8(<span class="hljs-string">b"default.png"</span>),
    };
    transfer::transfer(new_player, tx_context::sender(ctx));
    object::delete(old);
}
</code></pre>
<p>จบจริงๆละ รอบหน้าตัดเล็บรอได้เลย เพราะเราจะเริ่มลงมือเขียนโค๊ดกันละ ส่วนข้อมูลฉบับเต็มสามารถดูได้จากเอกสาร <a target="_blank" href="https://docs.sui.io/concepts/object-model">https://docs.sui.io/concepts/object-model</a></p>
<p><em>By Gang|ContributionDAO</em></p>
]]></content:encoded></item><item><title><![CDATA[ทำเว็บให้กระจายศูนย์ด้วย Walrus 🚀 คู่มือสำหรับมือใหม่]]></title><description><![CDATA[ช่วงหลังมานี้ โลกบล็อกเชนไม่ได้มีแค่เรื่องการโอนเหรียญหรือทำ DeFi อีกต่อไป แต่ยังมีความพยายามที่จะขยายขอบเขตไปถึงการเก็บไฟล์และข้อมูลขนาดใหญ่ด้วย เพราะบล็อกเชนแบบดั้งเดิมถูกออกแบบมาเพื่อเก็บ state และ transaction เป็นหลัก ไม่ได้เหมาะสำหรับข้อมูลขนาดใ...]]></description><link>https://onthemove.contributiondao.com/walrus-simple-usage</link><guid isPermaLink="true">https://onthemove.contributiondao.com/walrus-simple-usage</guid><category><![CDATA[onthemoveth]]></category><category><![CDATA[walrus]]></category><category><![CDATA[Sui]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Sat, 13 Sep 2025 08:12:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1757478238898/901f430d-3a99-44ac-b7d1-aaeb0758b33e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>ช่วงหลังมานี้ โลกบล็อกเชนไม่ได้มีแค่เรื่องการโอนเหรียญหรือทำ DeFi อีกต่อไป แต่ยังมีความพยายามที่จะขยายขอบเขตไปถึงการเก็บไฟล์และข้อมูลขนาดใหญ่ด้วย เพราะบล็อกเชนแบบดั้งเดิมถูกออกแบบมาเพื่อเก็บ state และ transaction เป็นหลัก ไม่ได้เหมาะสำหรับข้อมูลขนาดใหญ่เท่าไหร่นัก จึงเป็นที่มาของ Walrus  😎</p>
<p><strong>อ่าวละ Walrus คืออะไร?</strong></p>
<p>Walrus เป็น decentralized storage &amp; data availability protocol ที่ถูกออกแบบมาโดยเฉพาะเพื่อเก็บไฟล์ขนาดใหญ่ (หรือที่เรียกว่า <em>blobs</em>) เช่น รูปภาพ วิดีโอ ไฟล์เว็บ หรือ binary files อื่น ๆ</p>
<p>คุณสมบัติหลักของ Walrus คือ:</p>
<ul>
<li><p>Decentralized Storage – ไม่มี server กลาง ข้อมูลจะถูกกระจายไปเก็บตาม node ในเครือข่าย</p>
</li>
<li><p>Data Availability Layer – การันตีว่าไฟล์ที่ถูกเก็บไว้จะ “หาเจอและโหลดได้จริง” ไม่หายไปกลางทาง</p>
</li>
<li><p>Optimized for Blobs – ปรับแต่งให้เหมาะสมกับไฟล์ขนาดใหญ่ ต่างจาก blockchain ปกติที่เก็บข้อมูลแบบ state/transaction ซึ่งมีข้อจำกัดด้านขนาด</p>
</li>
<li><p>Economic Incentives – ผู้ที่เก็บไฟล์ (storage node) จะได้ค่าตอบแทนเป็น incentive ทำให้ระบบมีแรงจูงใจให้ข้อมูลคงอยู่เสมอ</p>
</li>
</ul>
<p><strong>อ่าวแล้ว Walrus กับ Sui (Mysten Labs) เกี่ยวข้องกันยังไงหละ?</strong></p>
<p>Walrus ถูกพัฒนาโดย Mysten Labs ทีมเดียวกับที่สร้าง Sui นั้นเอง โดยทั้งสองโปรเจกต์ออกแบบมาให้ ส่งเสริม กัน</p>
<ul>
<li><p>Sui → เป็น blockchain ที่ทำหน้าที่เก็บ state และ logic ของ smart contract (ข้อมูลที่ต้องการความถูกต้องและ consensus สูง)</p>
</li>
<li><p>Walrus → เป็น Decentralized Storage ไว้เก็บไฟล์ข้อมูลขนาดใหญ่ ที่ไม่เหมาะจะยัดลง blockchain โดยตรง โดย walrus ต่างจากที่เก็บข้อมูลแบบอื่นตรงที่มีคุณสมบัติ <em>availability และ verifiability</em></p>
</li>
</ul>
<p>สรุปง่ายๆคือ</p>
<ul>
<li><p>Walrus = Decentralized hosting/storage layer</p>
</li>
<li><p>Sui = Smart contract &amp; ownership layer</p>
</li>
</ul>
<hr />
<p>ตอนนี้เรารู้แล้วว่า Walrus คืออะไร และมันทำงานคู่กับ Sui ยังไง ทีนี้ก็ถึงเวลาลอง ลงมือทำจริง กันบ้าง 🎉 Use case ง่ายที่สุดคือการเอา เว็บไซต์เล็ก ๆ ของเราไปฝากบน Walrus แบบไม่ง้อเซิร์ฟเวอร์ และที่สำคัญคือ ไม่มีใครมาลบหรือปิดเว็บเราได้ 🔒</p>
<p>ถือเป็นโอกาสทองสำหรับใครที่อยากลองเล่นของใหม่ในโลกบล็อกเชน พร้อมแล้วไปดูกันว่า <strong>ขั้นตอนทำเว็บแรกของเราบน Walrus Site</strong> ต้องทำยังไงบ้าง 🚀</p>
<h1 id="heading-walrus-site"><strong>วิธีทำเว็บของตัวเองบน Walrus Site แบบง่าย ๆ 🚀</strong></h1>
<p>โลก Web3 ตอนนี้มาแรงสุด ๆ หนึ่งในของเล่นใหม่ที่น่าสนใจก็คือ Walrus Site ✨ มันคือการเอาเว็บไปฝากไว้บน เครือข่าย Walrus + Sui โดยไม่ต้องง้อเซิร์ฟเวอร์เอง พูดง่าย ๆ ก็คือ เราได้ โฮสติ้งแบบกระจายศูนย์ (decentralized hosting) ที่เข้าถึงได้ตลอดเวลา และไม่มีใครมาลบหรือปิดเว็บเราได้</p>
<p>ทำไม Walrus Site ถึงน่าสนใจ? 🤔</p>
<ul>
<li><p>Serverless Deployment — อัปโหลดไฟล์เว็บตรงไปบน blockchain ไม่ต้องมีเซิร์ฟเวอร์</p>
</li>
<li><p>Ownership &amp; Flexibility – เว็บเป็นของคุณโดยสมบูรณ์ สามารถโอนหรืออัปเดตได้ตลอด</p>
</li>
<li><p>NFT Integration – ผูกเว็บกับ Sui objects เช่น เว็บเฉพาะ NFT หรือโชว์คอลเลกชัน NFT ได้</p>
</li>
<li><p>High Availability — เพราะอยู่บนระบบกระจายศูนย์</p>
</li>
<li><p>SuiNS Domain — จดโดเมนผ่าน SuiNS</p>
</li>
</ul>
<p>พร้อมไหม… มาเริ่มกันเลย (คำสั่งอาจจะแตกต่างกันไปแต่ละ OS นะ ในที่นี้เราใช้ Ubuntu ในการติดตั้ง)</p>
<ol>
<li><p><strong>เตรียมเครื่องมือที่จำเป็น</strong></p>
<ul>
<li><p>ติดตั้ง Rust</p>
<pre><code class="lang-powershell">  brew install rustup 
  rustup<span class="hljs-literal">-init</span>
</code></pre>
</li>
<li><p>ติดตั้ง SUI CLI</p>
<pre><code class="lang-powershell">  brew install sui
  sui -<span class="hljs-literal">-version</span> <span class="hljs-comment">#ตรวจสอบว่าติดตั้งถูกต้องใหม</span>
</code></pre>
</li>
<li><p>สร้าง SUI wallet (เลือก ed25519 นะ)</p>
<pre><code class="lang-powershell">  sui client <span class="hljs-built_in">new-env</span> -<span class="hljs-literal">-alias</span> mainnet -<span class="hljs-literal">-rpc</span> https://fullnode.sui.io:<span class="hljs-number">443</span> <span class="hljs-comment">#เพื่อ activate client</span>
</code></pre>
<p>  ขั้นตอนนี้สำคัญมากคือต้องเก็บ <strong>Secret Recovery Phrase</strong> ไว้นะห้ามหายเด็ดขาด และต้องมั่นใจว่าเก็บในสถานที่ ที่ปลอดภัยด้วย ซึ่งเราสามารถนำ Secret Phrase ไป import เข้ากระเป๋าได้ ซึ่งในที่นี้เเนะนำตัวนี้เลย <a target="_blank" href="https://slush.app/"><strong>Slush — A Sui wallet</strong></a> เป็นกระเป๋าหลักของ SUI</p>
</li>
<li><p>ติดตั้ง Walrus cli (<a target="_blank" href="https://docs.wal.app/usage/setup.html">https://docs.wal.app/usage/setup.html</a>)</p>
<pre><code class="lang-powershell">  <span class="hljs-built_in">curl</span> <span class="hljs-literal">-sSf</span> https://install.wal.app | sh 
  walrus -<span class="hljs-literal">-help</span> <span class="hljs-comment">#ตรวจสอบว่าลงสำเร็จแล้ว</span>
</code></pre>
</li>
<li><p>ติดตั้ง site-builder</p>
<p>  สำหรับ platform อื่นสามารถดูวิธีการติดตั้งเพิ่มเติมได้ที่  <a target="_blank" href="https://docs.wal.app/walrus-sites/tutorial-install.html">https://docs.wal.app/walrus-sites/tutorial-install.html</a></p>
<pre><code class="lang-powershell">  SYSTEM=macos<span class="hljs-literal">-x86_64</span>  <span class="hljs-comment"># เปลี่ยนตาม OS ของคุณ</span>
  <span class="hljs-built_in">curl</span> https://storage.googleapis.com/mysten<span class="hljs-literal">-walrus</span><span class="hljs-literal">-binaries</span>/site<span class="hljs-literal">-builder</span><span class="hljs-literal">-testnet</span><span class="hljs-literal">-latest</span>-<span class="hljs-variable">$SYSTEM</span> <span class="hljs-literal">-o</span> site<span class="hljs-literal">-builder</span>
  <span class="hljs-built_in">mv</span> site<span class="hljs-literal">-builder</span> /usr/local/bin/
</code></pre>
<p>  ตรวจสอบว่าติดตั้งถูกต้องไหม</p>
<pre><code class="lang-powershell">  site<span class="hljs-literal">-builder</span> -<span class="hljs-literal">-help</span>
</code></pre>
</li>
<li><p>เตรียม walrus site config</p>
<pre><code class="lang-powershell">  mkdir <span class="hljs-literal">-p</span> ~/.config/walrus/
  <span class="hljs-built_in">curl</span> https://raw.githubusercontent.com/MystenLabs/walrus<span class="hljs-literal">-sites</span>/refs/heads/mainnet/sites<span class="hljs-literal">-config</span>.yaml <span class="hljs-literal">-o</span> ~/.config/walrus/sites<span class="hljs-literal">-config</span>.yaml
</code></pre>
</li>
</ul>
</li>
<li><p><strong>เตรียมเว็บที่จะ deploy</strong></p>
<ul>
<li><p>เว็บต้องมี index.html เป็น entry point</p>
</li>
<li><p>ตัวอย่าง repo ของ Walrus โดยเราจะใช้เว็บตัวอย่าง <strong>walrus-snake</strong> สำหรับฝึก deploy กัน</p>
</li>
<li><pre><code class="lang-powershell">          git clone https://github.com/MystenLabs/example<span class="hljs-literal">-walrus</span><span class="hljs-literal">-sites</span>.git
          <span class="hljs-built_in">cd</span> example<span class="hljs-literal">-walrus</span><span class="hljs-literal">-sites</span>
</code></pre>
</li>
</ul>
</li>
<li><p><strong>เตรียมเหรียญเพื่อใช้เป็นค่าใช้จ่ายสำหรับ Deploy</strong></p>
<ul>
<li><p>ก่อนอื่นเลยเราต้องมีเหรียญ SUI และ Warlus ก่อนสามารถเข้าไปหาซื้อได้ตาม exchange ต่างๆ ดูข้อมูลได้จากที่นี้ <a target="_blank" href="https://coinmarketcap.com/currencies/sui/">https://coinmarketcap.com/currencies/sui/</a> และ <a target="_blank" href="https://coinmarketcap.com/currencies/walrus-xyz/">https://coinmarketcap.com/currencies/walrus-xyz/</a></p>
</li>
<li><p>หลังจากนั้นให้ดาวโหลด wallet extension ซึ่งในที่เราเลือกใช้ <strong>Slush — A Sui wallet</strong> (<a target="_blank" href="https://slush.app/">https://slush.app/</a>) เมื่อติดตั้ง wallet extension เสร็จเรียบร้อยแล้ว ให้ import ค่า <strong>Secret Recovery Phrase</strong> จากขั้นตอนก่อนหน้า เเล้วทำการโอนเงินจาก exchange ไปที่กระเป๋าของเรานั้นเอง</p>
<p>  <em>(ส่วนนี้สำหรับมือใหม่ อาจจะ งงๆ นิดหน่อย เเต่โดยหลักการคือ เราต้องหาซื้อ token เพื่อมาใช้สำหรับการทำธุรกรรมเเละค่าธรรมเนียมนั้นเอง แนะนำว่าให้ซื้อประมาณ 1 SUI และ 2 Warlus tokens น่าจะเพียงพอละ หรือใครไม่มีก็ comment กระเป๋ามาเลย เดียวเราโอนให้เพื่อการเรียนรู้ :P )</em></p>
</li>
</ul>
</li>
<li><p><strong>อัปเว็บขึ้น Walrus</strong></p>
<ul>
<li><p>ใช้คำสั่งด้านล่างนี้เพื่อ upload เว็บไซด์ขึ้นไปเก็บยัง walrus (ใช้ SUI และ Walrus เล็กน้อยสำหรับค่าธรรมเนียม)</p>
<pre><code class="lang-powershell">  site<span class="hljs-literal">-builder</span> publish ./walrus<span class="hljs-literal">-snake</span> -<span class="hljs-literal">-epochs</span> <span class="hljs-number">1</span>
</code></pre>
<p>  <em>(epochs คืออายุของการเก็บรักษา object นั้น โดย 1 epoch จะมีค่าประมาณ 14 วัน)</em></p>
</li>
<li><p>หลังเสร็จ คุณจะได้ <strong>site object ID</strong> + <strong>URL</strong> ในขั้นตอนการทดสอบนั้นเราจะทำไป 2 แบบคือ ทดสอบผ่าน local host หรือทำการผูก site object id กับ SuiNs ซึ่งเราจะไปกันให้สุด นั้นคือไปจด Domain Name กันต่อ สำหรับใครอยากทดสอบผ่าน local host สามารถดูขั้นตอนได้ที่นี้นะ <a target="_blank" href="https://docs.wal.app/walrus-sites/portal.html#running-a-local-portal">https://docs.wal.app/walrus-sites/portal.html#running-a-local-portal</a></p>
</li>
</ul>
</li>
<li><p><strong>การต่ออายุการใช้งาน</strong></p>
<ul>
<li><p>แน่นอนว่าเมื่อครบตามจำนวน epoch ที่กำหนดไว้ เราก็ต้องไม่ลืมต่ออายุการใช้งาน ซึ่งสามารถทำโดยเรียกใช้คำสั่งต่อไปนี้</p>
<pre><code class="lang-powershell">  site<span class="hljs-literal">-builder</span> update -<span class="hljs-literal">-epochs</span> <span class="hljs-number">1</span> ./walrus<span class="hljs-literal">-snake</span> <span class="hljs-number">0</span>xYourSiteObjectID
</code></pre>
</li>
<li><p>ซึ่งก่อนทำการต่ออายุ จำเป็นจะต้องมีการเเก้ไขไฟล์ ก่อนที่จะทำการต่ออายุด้วยนะครับ หรือสามารถใช้ parameter</p>
</li>
</ul>
</li>
</ol>
<p>    <code>--force หากต้องการต่ออายุโดยไม่เปลี่ยนเนื้อหา ซึ่งเราสามารถต่ออายุได้สูงสุด 53 epochs (~2 ปี)</code></p>
<ol start="6">
<li><p><strong>ผูกชื่อเว็บให้อ่านง่ายด้วย SuiNS</strong></p>
<p> SuiNS คือ (Sui Name Service) คือ ระบบ Domain Name Service บนเครือข่าย Sui เปรียบเทียบได้เหมือน Domain name ที่เราคุ้นชินกันอยู่แล้วนั้นเอง พร้อมแล้วไปเริ่มกันเลย</p>
<ul>
<li><p>ไป <a target="_blank" href="https://suins.io/">https://suins.io/</a> แล้วเชื่อมกับ wallet extension ที่ได้ทำการติดตั้งก่อนหน้า หลังจากนั้นค้นหา domain name ที่เราต้องการ</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757751228604/fe56b222-a702-4e4e-baed-cf1f7492c390.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>ในกรณีที่ Domain name ว่างเราก็สามารถที่จะเลือกซื้อ Domain เหล่านั้นได้ ทำการจ่ายเงินให้เรียบร้อย</p>
</li>
<li><p>ไปที่หน้า <a target="_blank" href="https://suins.io/account/my-names#your_name">https://suins.io/account/my-names#your_name</a> ทำการผูก site object ID เข้ากับ Domain name เป็นอันเสร็จสิ้น</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757750662679/464138c8-5e21-40fc-896e-e36f357d359e.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ol>
<hr />
<p>เป็นไงครับ เหนื่อยกันไหมขั้นตอนอาจจะเยอะเพราะมีการติดตั้งหลายอย่างในครั้งแรก เเละนี้คือผลลัพธ์ที่เราได้มา <a target="_blank" href="https://onthemove.wal.app/">https://onthemove.wal.app/</a> เว็บไซด์ที่จะไม่มีวันล่มอีกต่อไป ใครลองแล้วได้เว็บอะไรกันบ้าง เอามาแชร์ในคอมเมนต์ได้นะ</p>
<p>นี้เป็นเพียงตัวอย่างเริ่มต้นเท่านั้นนะ เดียวเจอกันใหม่รอบหน้า มาดูกันว่าเราจะพัฒนา dApp อะไรดี เพื่อดึงความสามารถของเจ้าสิงโตทะเล ให้ทำงานได้อย่างเต็มประสิทธิภาพ เจอกันนนน</p>
<p><em>By Tee | ContributionDAO</em></p>
]]></content:encoded></item><item><title><![CDATA[สวัสดี Move]]></title><description><![CDATA[ทุกคนที่เป็นนักพัฒนาซอฟเเวร์ คงคุ้นชินกับภาษาต่างๆในการพัฒนาเเอพพลิเคชั่น เช่น Typescript , Golang, Python กันเป็นเรื่องปกติแล้วใช่ม๊าา เช่น

เว็บแอป: เราเขียน API ด้วย Express (Node.js) หรือ Spring (Java) ต่อกับ Database แล้วส่ง JSON ให้ frontend

ร...]]></description><link>https://onthemove.contributiondao.com/hello-move</link><guid isPermaLink="true">https://onthemove.contributiondao.com/hello-move</guid><category><![CDATA[onthemoveth]]></category><category><![CDATA[Sui]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Mon, 08 Sep 2025 06:43:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1757324391121/821b13a0-b50d-480d-bbd7-e7c9f98c9c04.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>ทุกคนที่เป็นนักพัฒนาซอฟเเวร์ คงคุ้นชินกับภาษาต่างๆในการพัฒนาเเอพพลิเคชั่น เช่น Typescript , Golang, Python กันเป็นเรื่องปกติแล้วใช่ม๊าา เช่น</p>
<ul>
<li><p><strong>เว็บแอป</strong>: เราเขียน API ด้วย Express (Node.js) หรือ Spring (Java) ต่อกับ Database แล้วส่ง JSON ให้ frontend</p>
</li>
<li><p><strong>ระบบสมาชิก (Authentication/Authorization)</strong>: มีการ login, JWT, session, role-based access ที่เราคุมได้หมด</p>
</li>
<li><p><strong>Business Logic</strong>: จะเขียน rule การคิดเงิน, การจัดการ order, การคำนวณส่วนลด ฯลฯ ก็แค่เพิ่ม logic เข้าไปใน backend</p>
</li>
<li><p><strong>Database</strong>: ทุกอย่างสุดท้ายจบลงที่ row/column ใน SQL หรือ document ใน MongoDB</p>
</li>
<li><p><strong>Deployment</strong>: เราเลือกว่าจะ deploy บน AWS, GCP หรือ DigitalOcean จะ scale server ยังไงก็อยู่ที่เรา</p>
</li>
</ul>
<p>แต่รู้ไหมว่าในโลกของ Blockchain ภาษาหลักในการพัฒนาสิ่งที่เรียกว่า Smart Contract นั้น จะเป็นอีกโลกนึง แล้ว Smart contract คืออะไรละ? ต่างจาก สิ่งที่ทุกวันนี้เราเขียน Web application หรือ Backend services ยังไงกันนะ? เอาเเบบไม่ยาวเกินไป Smart contract คือชุดของโปรเเกรมที่ถูกติดตั้งบนระบบของ blockchain สามารถทำงานได้ด้วยตัวมันเองตามเงื่อนไขที่กำหนดเอาไว้ โดยไม่ต้องมีตัวกลาง เอาให้เห็นภาพกันอีกนิดนึด</p>
<p>ในระบบธนาคารเวลาเราโอนเงิน</p>
<ul>
<li><p>ใน Web 2.0 → คุณจะต้องเก็บยอดหรือรายการลงในฐานข้อมูลใช่ม่ะ แล้วก็ต้องเขียนโปรเเกรมเพื่อเรียกดูข้อมูล หรือเวลาจะโอนเงินก็ต้องทำผ่าน API ที่มี server เป็นตัวกลาง</p>
</li>
<li><p>ใน Web 3.0 → คุณเขียน smart contract บอกว่า ยอดเงินของใครเท่าไหร่ วิธีการโอนเงิน แล้ว deploy บน blockchain เวลาเราจะเรียกดูยอดคงเหลือ หรือจะทำธุรกรรม ก็สามารถทำได้ทันที โดยไม่ต้อผ่านตัวกล่าว เเละยังสามารถตรวจสอบข้อมูลเหล่านั้นได้ตลอดเวลาอีกทีด้วย</p>
</li>
</ul>
<p>ซึ่งภาษาที่ใช้สำหรับการพัฒนา Smart contract ก็จะมี Solidity (EVM เช่น Ethereum), Rust (Solana), Move ซึ่งจะเป็นพระเอกที่เราจะพูดถึงในวันนี้</p>
<p><strong>Move คืออะไร</strong></p>
<p>Move เป็นภาษาโปรแกรมที่ถูกออกแบบมาเพื่อการพัฒนา <strong>Smart Contract บน Blockchain</strong> โดยเริ่มต้นจาก Libra/Diem ของ Facebook/Meta และปัจจุบันถูกนำไปใช้ในหลาย Blockchain เช่น Aptos และ Sui จุดเด่นของ Move คือการสร้าง <strong>“Resource-Oriented Programming”</strong> หรือแนวคิดที่ให้ทรัพยากรดิจิทัล (เช่น Token, NFT) มีความปลอดภัยเหมือนกับการถือครองสินทรัพย์จริง ๆ ไม่สามารถคัดลอก ลบ หรือสร้างซ้ำโดยไม่ได้ตั้งใจ</p>
<p><strong>ทำไมต้อง Move</strong></p>
<p>จุดเด่น</p>
<ul>
<li><p><strong>ปลอดภัยกว่า Solidity</strong>: Move ออกแบบมาเพื่อลดปัญหาการเขียนโค้ดผิดพลาดที่มักเกิดใน Solidity เช่น การ Re-entrancy Attack หรือ Overflow</p>
</li>
<li><p><strong>Resource-Oriented</strong>: การจัดการ Asset เป็นของจริง (First-class) ไม่เหมือนตัวเลขใน Database ลดความเสี่ยงเรื่องการ Duplicate</p>
</li>
<li><p><strong>เข้าใจง่ายสำหรับ Developer ที่เคยทำ Web2</strong>: Syntax คล้าย Rust/C จึงไม่ยากสำหรับคนที่คุ้นกับภาษา typed language</p>
</li>
<li><p><strong>ทดสอบง่าย</strong>: โค้ด Move ถูกแยกเป็นโมดูล ทำให้เขียน test และ reuse code ได้ชัดเจน</p>
</li>
</ul>
<p>จุดด้อย</p>
<ul>
<li><p><strong>Ecosystem ยังเล็ก</strong> เมื่อเทียบกับ Solidity ที่มี DeFi/NFT/DAO Tools ครบวงจร</p>
</li>
<li><p><strong>Learning Curve ใหม่</strong> เพราะแนวคิด Resource-Oriented ไม่เหมือนกับการเขียนโปรแกรมทั่วไป</p>
</li>
<li><p><strong>เครื่องมือยังไม่หลากหลาย</strong> เมื่อเทียบกับ Ethereum Ecosystem ที่มีทั้ง Hardhat, Foundry, Truffle</p>
</li>
</ul>
<p><strong>Move ต่างจาก Solidity ยังไง</strong></p>
<ul>
<li><p><strong>การจัดการ Asset</strong>: Solidity ใช้ตัวเลข (uint) แทน Token balance แต่ Move มอง Asset เป็น Resource จริง ๆ ไม่สามารถ Duplicate ได้</p>
</li>
<li><p><strong>ความปลอดภัยโดย Design</strong>: Move ป้องกัน Error หลายอย่างตั้งแต่ Compile-time ในขณะที่ Solidity ต้องใช้ Audit/Library เสริม</p>
</li>
<li><p><strong>การเรียนรู้</strong>: Solidity ใกล้เคียงกับ JavaScript มาก (คน Web2 ที่เป็น JS dev จะเรียนรู้ได้ไว) แต่ Move คล้าย Rust ที่ strict กว่า ทำให้ปลอดภัยแต่ต้องใช้เวลาเรียนรู้</p>
</li>
</ul>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>คุณสมบัติ</strong></td><td><strong>Solidity (Ethereum)</strong></td><td><strong>Move (Aptos, Sui)</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Asset Model</td><td>Balance ใน uint</td><td>Resource-Oriented</td></tr>
<tr>
<td>ความปลอดภัย</td><td>อาศัย Audit/Lib</td><td>Built-in Safety</td></tr>
<tr>
<td>ความนิยม</td><td>สูง (DeFi/NFT ecosystem ใหญ่)</td><td>กำลังเติบโต</td></tr>
<tr>
<td>รูปแบบของ Syntax</td><td>คล้าย JS</td><td>คล้าย Rust/C</td></tr>
<tr>
<td>เครื่องมือในการพัฒนา</td><td>Hardhat, Foundry, OpenZeppelin</td><td>Move CLI, SDK กำลังพัฒนา</td></tr>
</tbody>
</table>
</div><p><strong>Object-Oriented Blockchain คืออะไร?</strong></p>
<p>นี้คือหนึ่งในแนวคิดใหม่ที่เกิดขึ้นบน Blockchain ที่ใช้ Move คือ <strong>Object-Oriented Blockchain</strong> ซึ่งถูกนำมาใช้ใน <strong>Sui Blockchain</strong> นั้นจึงทำให้ Move แตกต่างจากภาษา Smart Contract อื่นๆ (อย่าง Solidity)</p>
<p>แนวคิดหลัก</p>
<p>แทนที่ทุกอย่างจะถูกเก็บเป็น <strong>บัญชี + ตัวเลข (balance)</strong> แบบใน Ethereum, Sui ใช้โมเดล <strong>Object</strong> ซึ่งคล้ายกับแนวคิดของ OOP (Object-Oriented Programming) คุ้นๆกันใช่ไหม</p>
<ul>
<li><p><strong>Object = Resource</strong>: แต่ละ Token, NFT หรือแม้แต่ Smart Contract instance จะถูกมองเป็น <strong>Object</strong> ที่มี state และ owner ชัดเจน</p>
</li>
<li><p><strong>Object ID</strong>: แต่ละ Object มี ID เฉพาะ ไม่ซ้ำกัน (เหมือน primary key ใน database)</p>
</li>
<li><p><strong>Ownership &amp; Transfer</strong>: การโอนย้าย Asset = การเปลี่ยน owner ของ Object ไปยัง address ใหม่</p>
</li>
<li><p><strong>Composable</strong>: Object สามารถซ้อนกันได้ เช่น เกมสามารถสร้าง “ตัวละคร” ที่เป็น Object และเก็บ “Item” ที่เป็น Object ภายในได้อีก</p>
</li>
</ul>
<p>จุดเด่น</p>
<ul>
<li><p><strong>การประมวลผลแบบขนาน (Parallel Execution)</strong>: เพราะ Object แต่ละตัวไม่แชร์ state กัน จึงสามารถทำธุรกรรมพร้อมกันได้ (TPS หรือ จำนวนธุรกรรมต่อวินาทีสูง)</p>
</li>
<li><p><strong>ชัดเจนเรื่องสิทธิ์ความเป็นเจ้าของ (Ownership)</strong>: ง่ายต่อการเขียน DApp ที่มี NFT, เกม หรือ RWA เพราะ Object เป็นตัวแทนสินทรัพย์จริง</p>
</li>
<li><p><strong>ใกล้เคียง OOP สำหรับ Web2 Developer</strong>: นักพัฒนา Web2 ที่คุ้นกับ Class/Object จะเข้าใจโมเดลนี้ได้เร็ว</p>
</li>
</ul>
<p>จุดด้อย</p>
<ul>
<li><p><strong>แนวคิดใหม่ ต้องเปลี่ยนมุมมอง</strong>: จาก Account-Based → Object-Based</p>
</li>
<li><p><strong>Ecosystem ยังเล็กกว่า Ethereum</strong>: Tools และ Lib สำหรับ Object-Oriented ยังมีไม่มาก</p>
</li>
<li><p><strong>ความซับซ้อนของการจัดการ Object</strong>: ต้องออกแบบระบบเก็บ, อ้างอิง และ Transfer Object ให้ดี ไม่งั้นจะทำงานยุ่งยาก</p>
</li>
</ul>
<p>มาดูตัวอย่างกันดีกว่า</p>
<p>Solidity (Ethereum Model)</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Balance = ตัวเลขใน mapping</span>
mapping(<span class="hljs-function"><span class="hljs-params">address</span> =&gt;</span> uint) balances;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transfer</span>(<span class="hljs-params">address to, uint amount</span>) <span class="hljs-title">public</span> </span>{
    <span class="hljs-built_in">require</span>(balances[msg.sender] &gt;= amount);
    balances[msg.sender] -= amount;
    balances[to] += amount;
}
</code></pre>
<p>Move (Sui Object-Oriented Model)</p>
<pre><code class="lang-rust">module example::coin {
    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Coin</span></span> has key {
        id: UID,          <span class="hljs-comment">// Object ID</span>
        value: <span class="hljs-built_in">u64</span>,       <span class="hljs-comment">// จำนวนเงิน</span>
        owner: address,   <span class="hljs-comment">// เจ้าของ</span>
    }
    public entry fun transfer(coin: Coin, to: address): Coin {
        <span class="hljs-comment">// เปลี่ยนเจ้าของ Object</span>
        Coin { id: coin.id, value: coin.value, owner: to }
    }
}
</code></pre>
<p>จะเห็นได้ว่า ในโมเดลของ Sui นั้น Coin แต่ละเหรียญคือ Object จริง ๆ ไม่ใช่แค่เลข balance ใน mapping</p>
<p>นอกจากนี้ยังมีส่วนอื่นๆที่น่าสนใจอีกด้วยนะ</p>
<ul>
<li><p>Move มี <strong>Formal Verification</strong> ทำให้สามารถตรวจสอบ Smart Contract เชิงคณิตศาสตร์ได้ ลดความเสี่ยงจากบัค</p>
</li>
<li><p>Move กำลังได้รับความนิยมใน Blockchain รุ่นใหม่ที่เน้น Performance และ Security เช่น Aptos (Parallel Execution) และ Sui (Object-Oriented Blockchain)</p>
</li>
</ul>
<p><strong>ทำไม Web2 Developer ควรเริ่มเรียน Move ตอนนี้</strong></p>
<p>ถ้าคุณเป็น Web2 Developer ที่คุ้นกับภาษาจำพวก JavaScript, Java, Python หรือแม้แต่ Rust การก้าวเข้าสู่โลก Web3 ด้วย <strong>Move Language</strong> จะเป็นโอกาสที่เหมาะ เพราะ Move ไม่ได้เป็นแค่ภาษาเขียน Smart Contract ธรรมดา แต่ถูกออกแบบมาเพื่อแก้จุดอ่อนที่เราเห็นใน Ethereum และ Solidity มาอย่างยาวนาน</p>
<ul>
<li><p><strong>ปลอดภัยกว่า</strong>: Resource-Oriented ทำให้ Asset บนบล็อกเชนถูกจัดการเหมือน “ของจริง” ไม่มีการ duplicate หรือสูญหายง่าย ๆ</p>
</li>
<li><p><strong>ใกล้เคียง OOP</strong>: ถ้าคุณเคยเขียน Class/Object ใน Web2 การเรียนรู้ Object-Oriented Blockchain ของ Sui จะทำให้คุณรู้สึกว่าโค้ด Smart Contract ไม่ได้ต่างจากการออกแบบระบบ Web app ที่คุณคุ้นเคย</p>
</li>
<li><p><strong>อนาคตของ Blockchain รุ่นใหม่</strong>: Move กำลังถูกใช้ใน Blockchain ที่เน้น performance และ security เช่น Aptos และ Sui ซึ่งกำลังดึงดูดนักพัฒนาและนักลงทุนมากขึ้นเรื่อย ๆ</p>
</li>
<li><p><strong>First-Mover Advantage</strong>: Solidity Dev มีเยอะแล้ว แต่ Move Dev ยังมีน้อยมาก — การเรียนรู้ตอนนี้ทำให้คุณได้เปรียบในตลาดงาน และเปิดโอกาสสร้าง Product ที่แตกต่างได้ก่อนใคร</p>
</li>
</ul>
<p>สรุปสั้นเลยๆนะ ถ้าคุณเป็น Web2 Developer ที่อยากก้าวเข้าสู่ Web3 ด้วยภาษาใหม่ที่ทั้งปลอดภัย เข้าใจง่าย และกำลังเติบโต <strong>Move คือภาษาที่ควรเริ่มต้นตอนนี้เลย</strong> เราสิ้นสุดการเกริ่นเกี่ยวกับ Move เพียงเท่านี้เพราะต่อไปนี้ ต่อไปต่อจากนี้ เราจะดำดิ่งไปกับการเขียนโค๊ดละ ตัดเล็บรอได้เลย</p>
<p><em>By Gang | ContributionDAO</em></p>
]]></content:encoded></item><item><title><![CDATA[SUI Build Beyond]]></title><description><![CDATA[SUI Network คือ Blockchain Layer 1 แต่ก่อนที่จะไปดูว่า SUI คืออะไร เรามาเกริ่นสั้น ๆ เกี่ยวกับเทคโนโลยี Blockchain กันก่อน เพราะ SUI เป็นเพียงหนึ่งในหลายพันโปรเจกต์ที่เกิดขึ้นบนโลกนี้ ดังนั้นเริ่มจากพื้นฐานกันเลยดีกว่า

Blockchain คือเทคโนโลยีการเก็บ...]]></description><link>https://onthemove.contributiondao.com/sui-build-beyond</link><guid isPermaLink="true">https://onthemove.contributiondao.com/sui-build-beyond</guid><category><![CDATA[onthemove]]></category><category><![CDATA[Sui]]></category><dc:creator><![CDATA[On The Move Thailand]]></dc:creator><pubDate>Sat, 06 Sep 2025 03:55:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1757056856959/2489d399-5ad1-47f0-b8a3-dc3eea761262.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>SUI Network คือ Blockchain Layer 1 แต่ก่อนที่จะไปดูว่า SUI คืออะไร เรามาเกริ่นสั้น ๆ เกี่ยวกับเทคโนโลยี Blockchain กันก่อน เพราะ SUI เป็นเพียงหนึ่งในหลายพันโปรเจกต์ที่เกิดขึ้นบนโลกนี้ ดังนั้นเริ่มจากพื้นฐานกันเลยดีกว่า</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757130708371/6e1fe040-b87e-45c0-8706-f8611ce102b4.png" alt class="image--center mx-auto" /></p>
<p>Blockchain คือเทคโนโลยีการเก็บข้อมูลแบบกระจายศูนย์ ข้อมูลจะถูกจัดเก็บต่อกันเป็น “บล็อก” ไปเรื่อย ๆ จึงได้ชื่อว่า Blockchain นั่นเอง จุดเด่นคือ ไม่มีตัวกลาง, โปร่งใส, และ ตรวจสอบได้</p>
<p>ถ้าเปรียบเทียบให้เห็นภาพ:</p>
<ul>
<li><p>แอปทั่วไปที่เราใช้ทุกวันจะมีเซิร์ฟเวอร์ที่ถูกควบคุมโดยเจ้าของแอป → เราไม่สามารถเข้าไปตรวจสอบได้</p>
</li>
<li><p>แต่ใน Blockchain ใคร ๆ ก็ตรวจสอบข้อมูลได้ และยังคัดลอก (สำเนา) ข้อมูลมาเก็บไว้เองได้ทันที</p>
</li>
</ul>
<p>Blockchain Layer 1 คือโครงสร้างหลักของระบบบล็อกเชน ที่สามารถเก็บและยืนยันธุรกรรมได้ด้วยตัวเอง เหมือนเป็น “ระบบปฏิบัติการ” (OS) ของ Blockchain เลย ทำให้แอปต่างๆ สามารถมาสร้างพัฒนาต่อยอดบนระบบได้</p>
<p>ตัวอย่าง Layer-1 ที่หลายคนอาจเคยได้ยิน เช่น Bitcoin, Ethereum, Solana เป็นต้น</p>
<hr />
<p>SUI เป็น Layer 1 blockchain ที่ออกแบบมาเพื่อรองรับธุรกรรมจำนวนมาก (High throughput) และทำงานด้วยความเร็วสูง (Low latency) ค่าธรรมเนียมต่ำ เหมาะสำหรับการสร้าง dApps (Decentralized Application) ที่มอบประสบการณ์ใช้งานใกล้เคียงกับแอปพลิเคชันที่พวกเราใช้งานกันอยู่ทุกวันนี้ เอาละพวกเรามาเริ่มดูข้อมูลทั่วไปของ SUI กันก่อนดีกว่า</p>
<ul>
<li><p>SUI ถูกพัฒนาโดยบริษัท Mysten Labs ซึ่งเป็นบริษัทที่อดีตวิศวกรจาก Facebook หรือที่เรียกว่า Meta ในปัจจุบัน เป็นผู้ก่อตั้งในเดือนกันยายน 2021 โดยเป็นกลุ่มวิศวกรที่ดูแลโครงการ Novi/Diem นั้นเอง (Diem คือโครงการพัฒนาเกี่ยวกับ Stablecoin เเละ Novi คือโครงการพัฒนาเกี่ยวกับ Crypto wallet)</p>
</li>
<li><p>ในส่วนผู้ร่วมก่อตั้งนั้นจะมีทั้งหมด 4 ท่านด้วยกัน ซึ่งต้องบอกว่าแต่ละท่านมีประวัติการทำงานที่สุดโหดทั้งนั้น</p>
<ul>
<li><p>Evan Cheng (CEO) – เคยเป็น Head of R&amp;D ที่ Novi และ Technical Director ที่ Meta (<a target="_blank" href="https://x.com/evanweb3">https://x.com/evanweb3</a>)</p>
</li>
<li><p>Sam Blackshear (CTO) – เคยเป็นหัวหน้าฝ่ายวิศวกรรมที่ Novi และเป็นผู้เชี่ยวชาญด้านภาษา Move (<a target="_blank" href="https://x.com/b1ackd0g">https://x.com/b1ackd0g</a>)</p>
</li>
<li><p>Adeniyi Abiodun (CPO) – เคยเป็นหัวหน้าฝ่าย Product Development ที่ Novi (<a target="_blank" href="https://x.com/EmanAbio">https://x.com/EmanAbio</a>)</p>
</li>
<li><p>George Danezis (Chief Scientist) – เคยทำงานวิจัยที่ Novi (<a target="_blank" href="https://x.com/GDanezis">https://x.com/GDanezis</a>)</p>
</li>
</ul>
</li>
<li><p>ระดมทุนไปได้ทั้งสิ้นกว่า 400 ล้านเหรียญ</p>
</li>
<li><p>เปิดตัวไปเมื่อวันที่ 3 พฤษาคม 2023 (Mainnet TGE)</p>
</li>
</ul>
<p>SUI ใช้ระยะเวลาเพียง 2 ปีเท่านั้น ก็ได้กระโดดเข้ามาเป็น Layer 1 network เเถวหน้าของโลก ซึ่งปัจจุบันตัวโครงการมีมูลค่ากว่า 33 พันล้านเหรียญ อยู่ใน 10 อันดับแรกของโครงการที่มูลค่าสูงสุดในกลุ่มของ layer 1 ซึ่งปัจจุบันมีสินทรัพย์ที่ถูกฝากอยู่ในตัวโครงการ ถึง 2 พันล้านเหรียญเลยทีเดียว</p>
<p><a target="_blank" href="https://defillama.com/chain/sui?currency=USD&amp;tvl=true"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757152629353/6804256e-8cf8-4cbd-a196-d67545349c94.png" alt class="image--center mx-auto" /></a></p>
<p>จุดเด่นหลักๆของ SUI Network คือ</p>
<ul>
<li><p>Layer 1 Blockchain</p>
<p>  มีระบบฉันทามติและการตรวจสอบธุรกรรมของตัวเอง ไม่ต้องพึ่งพาเชนอื่น</p>
</li>
<li><p>ใช้ภาษา Move</p>
<p>  ภาษาที่เน้นความปลอดภัยและยืดหยุ่น เหมาะกับการจัดการทรัพย์สินดิจิทัล ซึ่งภาษา Move ถูกพัฒนามาจาก Rust อีกถอดนึง</p>
</li>
<li><p>Object-Centric Model</p>
<p>  ทรัพย์สินถูกเก็บในรูปแบบ วัตถุ (object) ทำให้รองรับการทำงานที่ซับซ้อนได้</p>
</li>
</ul>
<p>ทำไม SUI Network ถึงน่าสนใจนะเหรอ นี้คือ 4 เหตุผลหลักๆ</p>
<ol>
<li><p>เทคโนโลยีที่ออกแบบมาเพื่ออนาคต<br /> SUI ทำธุรกรรมได้เร็ว ค่าธรรมเนียมถูก รองรับคนใช้งานพร้อมกันเยอะ ๆ แบบไม่สะดุด</p>
</li>
<li><p>ประสบการณ์ผู้ใช้ที่ใกล้เคียง Web2<br /> การโอน การเล่นเกม หรือใช้งานแอปต่าง ๆ บน SUI แทบจะทันที เหมือนใช้แอปมือถือทั่วไป</p>
</li>
<li><p>Ecosystem ที่กำลังเติบโต<br /> มีทั้งเกม, NFT, DeFi ที่ถูกสร้างบน SUI Ecosystem เป็นจำนวนมาก</p>
</li>
<li><p>ทีมผู้ก่อตั้งที่มีประสบการณ์<br /> มาจากทีม Diem (Facebook) ที่เคยทำโปรเจกต์ใหญ่ระดับโลกมาก่อน เรียกได้ว่าเป็นตัวตึง</p>
</li>
</ol>
<p>เริ่มน่าสนใจขึ้นมาบ้างแล้วใช่ไหมละ… เกริ่นกันแค่นี้ก่อน ยังมีข้อมูลเชิงลึกอีกมากมาย เดียวเราจะเขียนเเยกเป็นเรื่องๆไปนะ โดยเฉพาะสายนักพัฒนา เราจะได้มาเรียน Move ด้วยกันจาก Zero to Hero ฝากกดติดตามไว้ด้วยน๊า…</p>
<p>Facebook group: <a target="_blank" href="https://www.facebook.com/groups/onthemoveth">https://www.facebook.com/groups/onthemoveth</a></p>
<p>X: <a target="_blank" href="https://x.com/onthemoveth">https://x.com/onthemoveth</a></p>
<p><em>By Por | ContributionDAO</em></p>
]]></content:encoded></item></channel></rss>