Building Gasless Payment System for WETH, DAI and USDC

Natthapach Anuwattananon
4 min readSep 17, 2022

--

สวัสดีครับ บทความนี้ก็เป็นบทความที่สองในซีรี่ย์ Building NFT Smart Contract for Real World Usecase นะครับ สำหรับบทความอื่นๆในซีรี่ย์นี้ก็ตามวาร์ปด้านล่างเลยครับ

หลังจากบทความที่แล้วเล่าถึงการใช้ Proxy เพื่อลด gas cost ในการ deploy contract ใหม่ในแต่ละครั้ง บทความนี้จะมาเล่าถึงการทำ Gasless Payment System ครับ ซึ่งก็คือการสร้างระบบการชำระเงิน โดยที่ผู้ใช้ไม่ต้องจ่ายค่า gas เองครับ

เทคนิคที่จะเล่าในบทความนี้ใช้ได้เฉพาะบน Polygon Mainnet เท่านั้นนะครับ

Why Gasless is matter?

Gas fee บน blockchain เป็นหนึ่งในคอนเซปที่สำคัญที่ทำให้ blockchain technology สามารถสเกลขึ้นมาได้ถึงระดับนี้ครับ แต่ในมุมของผม gas fee ก็เป็นอุปสรรคสำคัญในการสร้าง mass adoption สำหรับผู้ใช้ทั่วไปในวงกว้างด้วยครับ

ประเด็นแรก คือการที่ผู้ใช้ทั่วไปต้องถือ native token (MATIC, ETH, etc.) นอกเหนือจากเหรียญที่ต้องการใช้จ่าย ถือว่าเป็นความยุ่งยากสำหรับระดับ mass user มากครับ ทั้งในเรื่องการสื่อสารและการใช้งาน

ประเด็นที่สอง การที่ในยุคนี้สามารถโอนเงินให้เพื่อนโดยไม่เสียค่าธรรมเนียมได้แล้ว แต่การที่จะใช้จ่ายหรือโอนคริปโตกลับยังต้องเสียค่าธรรมเนียมอยู่ (แถมค่าธรรมเนียมที่ว่ายังแกว่งไปมาอีก) เรื่องนี้ในมุมของผู้ใช้ทั่วไปแล้วถือว่าค่อนข้างแย่มากทีเดียวครับ

นี้จึงเป็นที่มาของ gasless system และบทความนี้ครับ ซึ่งในบทความนี้จะเล่าถึงการ implement gasless payment ด้วยคอนเซป Meta Transaction นะครับ

Meta Transaction

โดยปกติแล้วการทำธุรกรรมบน EVM Blockchain ผู้ใช้ (sender) จะทำการ sign transaction และส่งไป execute บน blockchain เอง ซึ่งผู้ที่ execute transaction จะเป็นคนเสีย gas fee ครับ

Normal execute blockchain transaction

แล้วถ้า sender ไม่ใช่คน execute เองล่ะ? meta transaction ใช้คอนเซปนี้ในการ implement ครับ

ใน meta transaction จะเพิ่มคนกลางที่เรียกว่า gas relayer เข้ามาครับ และเปลี่ยนจากที่ sender จะต้อง sign transaction ทั้งหมด ไปเป็น sign แค่ request message แล้วส่งให้ relayer แทน

relayer จะเป็นคนนำ request มาสร้างเป็น transaction และส่งไป execute กับ smart contract เอง จากนั้น contract จะทำการ verify และทำงานตาม request ที่ sender sign มา

Meta transaction concept

meta transaction มี implement จริงในหลาย contract เช่น WETH, USDC, DAI และ Opensea Native NFT ครับ

นอกจากนี้ EIP standard ก็มีการนิยาม meta transaction เช่น EIP-2771 แต่ใน EIP-2771 นั้นมีการแยกออกมาอีก 1 party คือ Trusted Forwarder แต่ใน implement ที่กล่าวไว้ด้านบนจะรวม trusted forwarder เข้ากับตัว smart contract เลย

EIP-712 Typed Data

ก่อนจะไปเริ่มลงมือทำกัน อยากจะมาแนะนำให้รู้จักกับ EIP-712 Typed Data กันก่อนครับ

โดยปกติแล้วเราจะใช้ crypto wallet (ex. metamask) เพื่อ sign อยู่สองอย่างครับ คือ transaction และ message ซึ่ง message ที่เราเห็นทั่วไปจะเป็นเพียงแค่ string ธรรมดาครับ (ex. ตอน login opensea จะมีมาให้ sign ข้อความยืนยันตัวตน)

แต่ message ที่เป็นแค่ string นั้นทำได้เพียงแค่ plain text ธรรมดา ทำให้มีข้อจำกัดเมื่อใช้กับข้อมูลที่มีความซับซ้อน (เช่นข้อมูลแบบ structural) ได้ลำบาก

ดังนั้นจึงได้มีการสร้างวิธีการ sign อีกแบบขึ้นมาครับ เรียกว่า signTypedData ซึ่งก็ตามชื่อเลยครับ ใช้สำหรับการ sign ข้อมูลที่มี type

ตัวอย่าง message แบบ structural ที่นำมา sign ด้วย signTypedData

จะเห็นได้ว่าข้อความที่นำมา sign นั้นมีความซับซ้อน แต่ก็อ่านง่ายกว่าการเป็นแค่ plain text ธรรมดาของ sign message มากครับ

ที่ต้องเล่าให้ฟังถึง Typed Data ก่อนก็เพราะว่า Meta Transaction นั้นก็ใช้ signTypedData ด้วยเช่นกัน เนื่องจาก request message นั้นเป็นข้อมูลที่ค่อนข้างซับซ้อนครับ

Building Gasless Payment

เอาล่ะ หลังจากผมเกริ่นมานาน เราจะเข้าสู่เนื้อหาหลักของบทความนี้กันครับ ในตัวอย่างนี้จะเล่าถึงการสั่งโอน (transfer) WETH จากกระเป๋าของผู้ใช้ ไปยังกระเป๋าปลายทาง (receiver) โดยที่ผู้ใช้จะไม่ต้องเสียค่า gas เองสัก wei เลยนะครับ

โครงสร้างของโปรเจคง่ายๆก็จะมีแค่ 2 ส่วนครับ คือ frontend เป็นหน้าสำหรับให้ผู้ใช้ sign message และ backend ที่นำ signature ที่ได้จาก frontend มา execute

Sign Data on Frontend

ผมจะไม่พูดถึงการ connect wallet และการวางโครงสร้างหน้าเว็บนะครับ ดังนั้นเราจะข้ามไปดูกันที่ ฟังก์ชั่น signMessage (line 37) กันเลยครับ

สำหรับฟังก์ชั่น signTypedData นั้นต้องการ arguments 3 ตัวครับ คือ domain , types และ value ซึ่งในฟังก์ชั่นนี้เราก็จะทำการเตรียมข้อมูลทั้งสามนี้กันครับ

Domain

เริ่มจาก domain กันก่อนครับ domain นั้นอธิบายง่ายๆว่าเป็นเหมือน metadata ของ typed data ครับ ซึ่งก็จะระบุว่าเป็น data สำหรับ contract ไหน อยู่บนเชนอะไร ทำนองนี้ครับ

วิธีการสร้าง domain นั้นสามารถ query ข้อมูลต่างๆออกมาจาก contract WETH ได้เลยครับ

การสร้าง domain นั้นต้องสร้างให้ตรงกับ format ของแต่ละ contract ครับ ซึ่งแต่ละ smart contract นั้นอาจมีโครงสร้าง Domain หรือวิธีการดึงข้อมูลมาสร้างเป็น domain ไม่เหมือนกันครับ เราสามารถไปดูได้จากตัวแปร DOMAIN_TYPEHASH และฟังก์ชั่น domainSeperator ครับ

DOMAIN_TYPEHASH in WETH contract

Types

ส่วนต่อมาคือ types ครับ types เป็นตัวนิยามว่า typed data ที่เราจะให้ user sign นั้นมีโครงสร้างข้อมูลเป็นอย่างไร ซึ่งในกรณีนี้ก็คือโครงสร้างของ meta transaction ครับ ซึ่งมี standard อยู่แล้ว ตรงนี้ผมก็จะ hard code ลงไปได้เลยครับ

ในส่วนของ types นั้นจริงๆแล้วก็มีระบุอยู่ใน smart contract เช่นกันครับ สามารถหาได้จากตัวแปร META_TRANSACTION_TYPEHASH หรือ struct MetaTransaction ครับ

META_TRANSACTION_TYPEHASH in WETH

สำหรับที่มาและความหมายของ domain และ TYPEHASH ต่างๆ ผมจะอธิบายแบบละเอียดในบทความหน้า เมื่อเราไป implement smart contract ที่ใช้ meta transaction กันเองนะครับ

Value (meta transaction)

เอาละ มาถึงส่วนสำคัญของเราจริงๆ คือ meta transaction ครับ ซึ่งโครงสร้างของ meta transaction นั้นมี 3 อย่างครับ คือ nonce , userAddress และ functionSignature

nonce นั้นคือ จำนวน meta transaction ที่ userAddress เคยทำกับ contract นั้นๆ ครับ โดยปกติแล้วค่า nonce จะถูกเก็บไว้ใน contract อยู่แล้วครับ เราสามารถ query ออกมาได้เลย

functionSignature คือการ encode function call data ที่ user ต้องการจะ execute ครับ ซึ่งในกรณีนี้ user ต้องการเรียกฟังก์ชั่น transfer เพื่อโอน WETH ไปยัง receiver ก็สามารถใช้ interface หรือ ABI encode ไปตรงๆได้เลยครับ

หลังจากเรียกใช้ก็จะได้หน้าต่างการ sign typed data แบบด้านล่างครับ เพียงเท่านี้เราก็จะได้ signature สำหรับส่งให้กับฝั่ง backend แล้วครับ

sign meta transaction for transfer WETH

Execute on Backend

สำหรับฝั่ง backend นั้นค่อนข้างง่ายครับ เพียงแค่รับ signature, address และ functionSignatureมา จากนั้นก็นำไปเรียกฟังก์ชั่น executeMetaTransaction ได้เลย

สำหรับการเรียกฟังก์ชั่นนั้น ขั้นตอนนี้จะต้องใช้ Gas Relayer หรือผู้ที่ออกค่า gas ตามที่เล่าไว้ด้านบนครับ เพียงเท่านี้ก็จะได้ระบบ Gasless Payment แล้วครับ

สำหรับตัวอย่าง transaction ที่สำเร็จจริงๆนะครับ 0xacfe3ad29ee604c3b2e5d6067867026c38decfbdb10846f473220e9081585c61

Others Usage

ในตัวอย่างนี้เป็นการเรียกฟังก์ชั่น transfer ตรงๆสำหรับการจ่ายเงินครับ แต่จะเห็นได้ว่า functionSignature นั้น เราสามารถ encode ฟังก์ชั่นอะไรก็ได้ครับ

ดังนั้นแล้วมีอีก design ที่พบเห็นได้ก็คือการใช้ meta transaction สำหรับการเรียกฟังก์ชั่น approve เพื่อ allowance ให้กับ smart contract อีกตัวนึง (ซึ่งแน่นอนครับ ท่ามาฐานในวงการ allowance infinity) จากนั้นก็จะทำการเรียก transferFrom จากใน smart contract แทนครับ

สำหรับการนำไปประยุกต์ใช้ก็ขึ้นอยู่กับ requirement และวิจารณญาณนะครับ

บทความนี้ก็จบลงเท่านี้ครับ หวังว่าจะเป็นแนวทางในการพัฒนาระบบใหม่ๆ ได้นะครับ สำหรับบทความหน้าจะพาไปเจาะลึกข้างใน smart contract ว่า Meta Transaction นั้นทำงานอย่างไร และจะพานำมาประยุกต์ใช้กับ NFT เพื่อสร้าง Gasless NFT กันครับ

Credit

ขอบคุณโค้ดต้นแบบจากน้องบิ๊ก Big Phanuwit ทีม frontend และน้องเกม Jitraponpoonsuk ทีม backend ด้วยนะครับ

--

--