สร้าง Smart Contract บน Near Protocol ด้วย Assembly Script

Published on
NEAR
near-example-guestbook-as
Discord

เป้าหมายคือ ต้องการสร้าง Smart Contract ด้วย Assembly Script ตั้งแต่เริ่มต้น โดยดูจาก NEAR Example - Guestbook ซึ่งจริงๆ แค่เรา clone โปรเจ็คมารัน ก็ได้แล้ว และถ้าอ่านโค๊ดก็เข้าใจไม่ยาก แต่อยากรู้ว่าถ้าเราเริ่มสร้างตั้งแต่เริ่มต้นเลย มันมีขั้นตอนยังไง มันก็จะทำให้เราเข้าใจมากขึ้นรึเปล่า ก็เลยเป็นที่มาของการทดลอง และสรุปเป็นบทความนี้ขึ้นมา

  • Near Examples - มีตัวอย่างหลายโปรเจ็ค สามารถไปดูได้ครับ (จริงๆ ตัว Project Counter น่าจะง่ายสุด เผื่อบางคนไม่รู้จะเริ่มตัวไหน)

สิ่งที่ต้องการเรียนรู้คือวิธีการสร้าง Smart Contract ด้วย AssemblyScript ทำให้ในบทความนี้ไม่ได้พูดถึงส่วนของ Frontend นะครับ แต่จะเป็นการ call / view ผ่าน near-cli เพื่อดูว่า ตัว smart contract เรา deploy ถูกต้อง

ทำไมถึงเลือก AssemblyScript?

จริงๆ แล้ว ไม่มีอะไรมาก แค่อยากลองดูและเปรียบเทียบกับ Rust และตัว AssemblyScript ก็เหมือน TypeScript มากๆ สิ่งที่ต่างคือ ตัว AssemblyScript เวลา compiled จะเป็นไฟล์ WebAssembly (WASM) ไม่ใช่ JavaScript แบบ TypeScript แต่ตัว syntax เรียกได้ว่าแทบจะเหมือนกัน ใครเขียน TypeScript ได้ ก็เขียน AssemblyScript ได้ครับ

Prerequisites

สิ่งที่ต้องมีเลย แน่ๆ คือ

  1. near-cli ถ้าไม่มีก็ติดตั้งด้วยคำสั่ง :
npm install --global near-cli
  1. Near Wallet ถ้าไม่มี? สมัคร Near Wallet (Testnet)

Setup Project

ขั้นตอนแรก ทำการสร้างโปรเจ็คขึ้นมา ผมตั้งชื่อว่า near-guestbook-as โดยข้างในจะมี folder คือ

  1. contract - เป็นไฟล์ที่เอาไว้เก็บ smart contract เขียนด้วย assemblyscript

เริ่มต้นสร้าง โฟลเดอร์ contract ก่อน

mkdir contract
cd contract

จากนั้น initial project (ข้างในโฟลเดอร์ contract นะครับ)

npm init -y

และติดตั้งพวก dependencies ลงไป

npm install @assemblyscript/loader@latest assemblyscript@latest asbuild near-cli near-sdk-as

Note: ตัว AS ล่าสุดคือ 0.20.4 และคิดว่าน่าจะมีอัพเดทเรื่อยๆ ณ ตอนเขียนบทความคือไม่มีปัญหา แต่ถ้าอนาคต มีเวอร์ชั่นใหม่ อาจจะมี breaking change ถ้าเจอปัญหา ลองดู changelog ดูนะครับ หรือ fix เวอร์ชั่นครับ

สำหรับ Mac M1 จะมีปัญหาการติดตั้ง near-sdk-as ขึ้นว่า Error: Unsupported platform: Darwin arm64 ให้ทำการติดตั้งด้วย คำสั่ง

npm install near-sdk-as --ignore-scripts

ต่อมาเราจะขึ้นโปรเจ็คด้วย asinit script:

npx asinit .

ตัว asinit จะทำการสร้าง folder และ config files ให้เรา auto เลย ก็แค่กด Y ยืนยัน

สามารถอ่าน Setting up a new project - AssemblyScript เพิ่มเติมได้ครับ วิธีการขึ้นโปรเจ็ค asinit

near-sdk-as

เราจะทำการปรับแก้ไฟล์นิดหน่อย เพื่อให้ตัว AssemblyScript มัน build และ compile ให้ตรงกับ near-sdk-as

สิ่งที่ต้องทำคือ ทำการแก้ไขไฟล์ asconfig.json โดยเปลี่ยนที่ตัว asinit generate มาให้ เป็น config ของ near-sdk-as แทน แบบนี้

asconfig.json
{
  "extends": "near-sdk-as/asconfig.json",
  "entry": "assembly/main.ts"
}
  • เราจะใช้ไฟล์ main.ts เป็นหลัก จึงต้องระบุ entry ให้มัน

ต่อมาเพิ่ม reference type ที่ไฟล์ assembly/as_types.d.ts

assembly/as_types.d.ts
/// <reference types="near-sdk-as/assembly/as_types" />

สร้าง Contract

ตัว Contract ที่เป็น AssemblyScript มี 2 ไฟล์ คือ main.ts และ model.ts

ตัว main.ts มีแค่นี้เอง (ถ้าลบ comment ออก)

main.ts
import { PostedMessage, messages } from "./model";

const MESSAGE_LIMIT = 10;

export function addMessage(text: string): void {
  const message = new PostedMessage(text);
  messages.push(message);
}

export function getMessages(): PostedMessage[] {
  const numMessages = min(MESSAGE_LIMIT, messages.length);
  const startIndex = messages.length - numMessages;
  const result = new Array<PostedMessage>(numMessages);
  for (let i = 0; i < numMessages; i++) {
    result[i] = messages[i + startIndex];
  }
  return result;
}
  • addMessage (call) - ก็เพิ่ม text ลง Storage (persistent collection)
  • getMessages (view) - ก็แสดงข้อมูล text (PostedMessage model) ตามจำนวน max ที่เป็นค่า constants ที่เรากำหนดไว้ อันนี้ก็ loop ธรรมดา

ส่วนตัว model.ts ก็ใช้ Collection ของ near-sdk-as

model.ts
import { context, u128, PersistentVector } from "near-sdk-as";

@nearBindgen
export class PostedMessage {
  premium: boolean;
  sender: string;
  constructor(public text: string) {
    this.premium =
      context.attachedDeposit >= u128.from("10000000000000000000000");
    this.sender = context.sender;
  }
}

export const messages = new PersistentVector<PostedMessage>("guestbook_prefix");
  • messages - เป็น PersistentVector ถ้าค่าเปลี่ยน เช่น .push() มันจะทำการเซฟลงใน storage ให้อัตโนมัติ (ลองดูไฟล์ main.ts ตรง function addMessage เราแค่ push เอง แต่ข้อมูลเซฟลง storage ให้เลย)
  • context.sender - ก็อารมณ์คล้ายๆ msg.sender ของ Solidity

ทดลอง build ดูว่าผ่านมั้ย เพิ่ม script ลงไปใน package.json ส่วน scripts

package.json
"scripts": {
  	"build": "asb --target debug",
		"build:release": "asb"
}

ลอง build ดู (ตัว asb จะเป็น AssemblyScript build tool นะครับ คล้ายๆกับ Cargo ที่รัน rustc (rust compiler) ในขณะที่ asb จะรัน asc (AssemblyScript Compiler))

npm run build

ถ้าไม่มีอะไรผิดพลาด ก็ build release:

npm run build:release

ตัวไฟล์ build ที่ได้ จะอยู่ folder /build/release/main.wasm นะครับ

Deploy & Test

สุดท้าย ทดสอบ deploy ตัว Contract โดยผมใช้ subaccount นะครับ ตอนนี้ผมใช้ supersaiyan.testnet

near create-account guestbook.supersaiyan.testnet --masterAccount supersaiyan.testnet --initialBalance 2

เวลา deploy ผมก็ใช้ คำสั่ง near deploy ก็เลยเอาไปใส่ใน scripts ใน package.json ซะ

package.json
"scripts": {
  "deploy": "near deploy --wasmFile build/release/main.wasm --accountId guestbook.supersaiyan.testnet"
}

ทดลอง Deploy

npm run deploy

ตัวอย่าง ผม deploy ลง testnet เรียบร้อย ได้ผลลัพธ์ดังนี้

Transaction Id H9CmfDFpwDMHg5gcEVS1gf6GTGD4oHFnMy9iaz4738dU
To see the transaction in the transaction explorer, please open this url in your browser
https://explorer.testnet.near.org/transactions/H9CmfDFpwDMHg5gcEVS1gf6GTGD4oHFnMy9iaz4738dU
Done deploying to guestbook.supersaiyan.testnet

ทดลอง View contract ที่ deploy ไป

near view guestbook.supersaiyan.testnet getMessages '{}'

ตอนแรกจะไม่มีข้อมูล จนกว่าจะ call เพื่อเพิ่มข้อมูล (ถ้าใครเทสหลังจากอ่านบทความนี้ จะมีข้อมูลนะครับ เพราะผม addMessage ไปเพิ่ม)

ลอง Call เพื่อเพิ่มข้อมูล

near call guestbook.supersaiyan.testnet addMessage '{"text": "Hello Devahoy"}' --account-id supersaiyan.testnet

เป็นอันเรียบร้อย ก็รู้สึกว่าการ setup ตัว AssemblyScript ก็ไม่ได้ยุ่งยากเท่าไหร่ สิ่งที่ต้องรู้เพิ่มคือพวก Collection ว่ามันมีอะไรบ้าง?

อ่านเพิ่มเติม

สุดท้าย Source Code ประกอบบทความ

สรุป

หลังจากบทความก่อนหน้า ผมได้ลอง Deploy Smart Contract แรกบน NEAR Protocol ไปแล้ว แต่เป็นแบบเขียนด้วย Rust วันนี้เลยลองด้วย AssemblyScript ดู ว่ามันง่ายกว่าจริงมั้ย ซึ่งหลายๆ คนก็แนะนำว่า ควรเริ่มจาก AssemblyScript เพื่อให้เข้าใจ concept คร่าวๆ แต่ถ้าไปใช้งานจริงๆ หรือทำ Smart Contract จริงๆ ตัว NEAR Official ยังแนะนำเลยว่าควรเป็น Rust และเหมือนอนาคตเค้าก็จะโฟกัสที่ Rust เป็นหลักครับ

หวังว่าการทดลองเล็กๆน้อยๆ ของผมจะเป็นประโยชน์กับหลายๆคนนะครับ ไม่รู้ว่าบทความแนวๆนี้จะมีคนชอบหรือเปล่า 😂

Happy Coding ❤️

Buy Me A Coffee
Authors
Discord