ทำ Frontend เชื่อมต่อ Wallet ด้วย Nextjs + Solana Wallet Adapter
วันนี้ลองทำ Frontend เพื่อใส่ปุ่ม Connect Wallet ให้กับเว็บไซต์ ซึ่งผมจะใช้ตัว Solana Wallet Adapter ข้อดีคือ มัน handle หลายๆ Wallet ให้เราเลย ไม่ว่าจะเป็น Phantom, Sollet, Ledger, Solflare, Math Wallet เป็นต้น
ซึ่งถ้าเราไม่ใช้ เราก็ต้องไป handle เอง เช่น ถ้าเราติดตั้ง Phantom แล้ว เราก็เข้าถึง global object ได้ เหมือนกับ Metamask ที่ inject ethereum
เป็น global object ใน window เช่นกัน
window.phantom;
// {solana: n}
ตัวอย่าง Solana Wallet Adapter
- จะเห็นว่า มี Popup และรายชื่อ Wallet ให้เราเลือก Connect ได้เลย
- จัดการ Auto connect ให้เรา
- มี UI รองรับทั้ง Material UI และ Ant Design
- รองรับ Frontend หลักๆ ทั้ง React, Vue และ Angular รวมถึง Svelte
สำหรับบทความนี้ ผมเลือกใช้
- Phantom - เป็นกระเป๋า Wallet บน Browser Extension.
- React + Nextjs - สำหรับ Stack ของ frontend ครับ
สร้างโปรเจ็ค Next.js
ทำการสร้างโปรเจ็คขึ้นมาโดยใช้ create-next-app
ครับ และผมเลือกเป็น TypeScript (หากไม่อยากเขียนด้วย TypeScript ก็เอา option ออกได้ครับ) หรืออ่านเพิ่มเติม Next.js - Getting Started
yarn create next-app --typescript
# หรือถ้าใช้ npx
npx create-next-app@latest --typescript
ผมตั้งชื่อว่า hello-sonala-wallet-adapter (แล้วแต่เลยว่าอยากได้ชื่ออะไร)
What is your project named? … hello-solana-wallet-adapter
เมื่อได้โปรเจ็คแล้ว ลองเปิดโปรเจ็ค แล้วลอง start development mode ดู
yarn dev
ต้องได้หน้าเว็บ starter ของ Next.js http://localhost:3000
เพิ่มปุ่ม Connect Wallet
ต่อมาติดตั้ง packages ของ Solana Wallet Adapter ที่จะใช้กัน
yarn add @solana/web3.js @solana/wallet-adapter-base @solana/wallet-adapter-wallets @solana/wallet-adapter-react
@solana/web3.js
- เป็นตัว Solana Web3 (ถ้าในบทความนี้หลักๆ ก็แค่ดึง url ซึ่งถ้า demo เรา hardcode ก็ได้)@solana/wallet-adapter-base
- ตัวนี้เป็น base ที่เป็น Adapter Inferface และ Utilities ต่างๆ@solana/wallet-adapter-react
- เป็นตัวจัดการ Context และ React Hooks@solana/wallet-adapter-react-ui
- เป็นตัว React UI เช่น Modal, MultiButton@solana/wallet-adapter-wallets
- เป็นตัวจัดการ Wallets ทุกๆตัวเลย (มี Tree shaking ตัวไหนไม่ได้ใช้ ก็ไม่ถูก include เข้ามาเวลา build)
ซึ่งถ้าใครไม่อยากใช้ adapter-wallets
ที่รวมทุก Wallets ก็สามารถใช้แยกได้เช่น @solana/wallet-adapter-phantom
หรือ @solana/wallet-adapter-sollet
จากนั้นที่ไฟล์ pages/_app.tsx
เพิ่ม Provider ลงไป
import { useMemo } from "react";
import type { AppProps } from "next/app";
import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
import {
ConnectionProvider,
WalletProvider,
} from "@solana/wallet-adapter-react";
import {
PhantomWalletAdapter,
MathWalletAdapter,
SolflareWalletAdapter,
} from "@solana/wallet-adapter-wallets";
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
import { clusterApiUrl } from "@solana/web3.js";
import "../styles/globals.css";
function MyApp({ Component, pageProps }: AppProps) {
const network = WalletAdapterNetwork.Devnet;
const endpoint = useMemo(() => clusterApiUrl(network), [network]);
const wallets = useMemo(() => {
return [
new PhantomWalletAdapter(),
new SolflareWalletAdapter({ network }),
new MathWalletAdapter(),
];
}, [network]);
return (
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets}>
<WalletModalProvider>
<Component {...pageProps} />
</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
);
}
export default MyApp;
อธิบายเพิ่มเติมนิดหน่อย คือ
const network = WalletAdapterNetwork.Devnet;
const endpoint = useMemo(() => clusterApiUrl(network), [network]);
เป็นการระบุ ให้ใช้ Network เป็น Devnet
const wallets = useMemo(() => {
return [
new PhantomWalletAdapter(),
new SolflareWalletAdapter({ network }),
new MathWalletAdapter()
];
}, [network]);
สร้างตัว Wallets ขึ้นมา เพื่อเป็นเมนูให้ User เลือกครับ ถ้าอยากมีตัวเลือกหลาย Wallet ก็ import เพิ่ม
return (
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets}>
<WalletModalProvider>
<Component {...pageProps} />
</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
);
ConnectionProvider
- กำหนดendpoint
จาก url ที่เรา set ค่าไว้WalletProvder
- กำหนด ตัวเลือก wallets ที่เราต้องการWalletModalProvider
- เป็นตัว Modal UI ของ React
ทีนี้ก็เพิ่มปุ่ม Connect Wallet ที่หน้า pages/index.tsx
ครับ โดยที่ผมไม่ได้เปลี่ยน content ที่ nextjs generate มาให้นะครับ แต่เอาปุ่มไปแทนที่ส่วนนี้
<p className={styles.description}>
Get started by editing <code className={styles.code}>pages/index.tsx</code>
</p>
ที่เหลือเหมือนเดิม เปลี่ยนเป็น
import {
WalletDisconnectButton,
WalletMultiButton,
} from "@solana/wallet-adapter-react-ui";
const Home: NextPage = () => {
return (
// โค๊ดอื่นๆ ก่อนหน้า
<div>
<WalletMultiButton />
<WalletDisconnectButton />
</div>
// ...
// โค๊ดอื่นๆ ไม่ได้แก้
)
}
แต่ว่าหน้าเว็บ ทำไมปุ่มเป็นแบบนี้ และก็กดอะไรไม่มีอะไรเกิดขึ้นเลย? จริงๆ มันได้ครับ และการทำงานปกติ เพียงแต่ว่าไม่มี css เท่านั้น
แก้ไขไฟล์ pages/_app.tsx
โดยเพิ่ม css ของ wallet-adapter-react-ui
ลงไปครับ
import"@solana/wallet-adapter-react-ui/styles.css";
เพียงแค่นี้ก็ได้แล้ว
แต่มันไม่ค่อยสวย ผมขอเพิ่ม css ให้มันนิดหน่อยนะครับ ให้มี spacing ของปุ่ม Connect และ Disconnect
.buttonContainer {
display: flex;
margin-top: 1em;
margin-bottom: 1em;
}
/* WalletMultiButton */
.buttonContainer > * {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
และเพิ่มคลาสไป ในไฟล์ pages/index.tsx
<div className={styles.buttonContainer}>
<WalletMultiButton />
<WalletDisconnectButton />
</div>
เป็นอันเรียบร้อย ซึ่ง Flow การทำงานของมันคือ Select Wallet ก่อน จากนั้นก็ Connect ครับ (สังเกตจาก icon wallet ที่เราเลือก)
เพื่อนๆ ลองนำไปใช้ ไปลองเล่นกันดูนะครับ ลองสลับ Network ลอง handle network ลองส่ง Transaction ดู หรือลองดูตัวอย่างอื่นๆ เพิ่มเติมได้ครับ ส่วนใหญ่จะเป็น Community ช่วยๆกันทำขึ้น เช่น Vue, Svelte
Happy Coding ❤️
- Authors
- Name
- Chai Phonbopit
- Website
- @Phonbopit