🎉 #Gate Alpha 第三届积分狂欢节 & ES Launchpool# 聯合推廣任務上線!
本次活動總獎池:1,250 枚 ES
任務目標:推廣 Eclipse($ES)Launchpool 和 Alpha 第11期 $ES 專場
📄 詳情參考:
Launchpool 公告:https://www.gate.com/zh/announcements/article/46134
Alpha 第11期公告:https://www.gate.com/zh/announcements/article/46137
🧩【任務內容】
請圍繞 Launchpool 和 Alpha 第11期 活動進行內容創作,並曬出參與截圖。
📸【參與方式】
1️⃣ 帶上Tag #Gate Alpha 第三届积分狂欢节 & ES Launchpool# 發帖
2️⃣ 曬出以下任一截圖:
Launchpool 質押截圖(BTC / ETH / ES)
Alpha 交易頁面截圖(交易 ES)
3️⃣ 發布圖文內容,可參考以下方向(≥60字):
簡介 ES/Eclipse 項目亮點、代幣機制等基本信息
分享你對 ES 項目的觀點、前景判斷、挖礦體驗等
分析 Launchpool 挖礦 或 Alpha 積分玩法的策略和收益對比
🎁【獎勵說明】
評選內容質量最優的 10 位 Launchpool/Gate
SIWE技術助力Dapp提升用戶身分驗證
SIWE:提升 Dapp 功能的關鍵技術
SIWE(Sign-In with Ethereum)是一種在以太坊上驗證用戶身分的方法,類似於發起錢包交易,證明用戶對錢包擁有控制權。目前的身分驗證方式非常簡單,只需在錢包插件中對信息進行籤名即可,大多數常見錢包插件都已支持。
本文主要討論以太坊上的籤名場景,不涉及其他區塊鏈如 Solana、SUI 等。
是否需要 SIWE
如果你的 Dapp 具有以下需求,可以考慮使用 SIWE:
但如果你的 Dapp 主要是查詢功能,比如類似 etherscan 的應用,可以不使用 SIWE。
你可能會疑惑,在 Dapp 上通過錢包連接後,不就已經證明了錢包所有權嗎?這個說法部分正確。對前端而言,錢包連接確實表明了身分,但對於需要後端支持的接口調用,僅傳遞地址是不夠的,因爲地址是公開信息,任何人都可以"借用"。
SIWE 的原理和流程
SIWE 流程可概括爲三個步驟:連接錢包 - 籤名 - 獲取身分標識。讓我們詳細了解這三個步驟。
連接錢包
連接錢包是常見的 Web3 操作,通過錢包插件可以在 Dapp 中連接錢包。
籤名
SIWE 中的籤名步驟包括獲取 Nonce 值、錢包籤名和後端籤名校驗。
獲取 Nonce 值需要調用後端接口。後端收到請求後,會生成隨機 Nonce 值並與當前地址關聯,爲後續籤名做準備。
前端獲取 Nonce 值後,需要構建籤名內容,包括 Nonce 值、域名、鏈 ID、籤名內容等,通常使用錢包提供的籤名方法進行籤名。
構建完籤名後,將其發送給後端。
獲取身分標識
後端校驗籤名通過後,會返回用戶身分標識,如 JWT。前端後續請求時帶上對應地址和身分標識,即可證明錢包所有權。
實踐
現有許多組件和庫支持快速接入錢包連接和 SIWE。我們的目標是讓 Dapp 能返回 JWT 用於用戶身分校驗。注意,此 demo 僅用於介紹 SIWE 基本流程,生產環境使用可能存在安全問題。
準備工作
本文使用 Next.js 開發應用,需要 Node.js 環境。使用 Next.js 的好處是可以直接開發全棧項目,無需分爲前後端兩個項目。
安裝依賴
首先安裝 Next.js,在項目目錄中執行:
npx create-next-app@14
按提示安裝完成後,進入項目目錄並運行:
npm run dev
根據終端提示,訪問 localhost:3000 即可看到基本的 Next.js 項目。
安裝 SIWE 相關依賴
SIWE 需要登入體系,因此需要連接錢包。這裏我們使用 Ant Design Web3,因爲:
在終端執行:
npm install antd @ant-design/web3 @ant-design/web3-wagmi wagmi viem @tanstack/react-query --save
引入 Wagmi
Ant Design Web3 的 SIWE 依賴 Wagmi 庫實現。我們需要在 layout.tsx 中引入相關 Provider,使整個項目都可以使用 Wagmi 提供的 Hooks。
首先定義 WagmiProvider 配置:
javascript "use client"; import { getNonce, verifyMessage } from "@/app/api"; import { Mainnet, MetaMask, OkxWallet, TokenPocket, WagmiWeb3ConfigProvider, WalletConnect, } from "@ant-design/web3-wagmi"; import { QueryClient } from "@tanstack/react-query"; import React from "react"; import { createSiweMessage } from "viem/siwe"; import { http } from "wagmi"; import { JwtProvider } from "./JwtProvider";
const YOUR_WALLET_CONNECT_PROJECT_ID = "c07c0051c2055890eade3556618e38a6"; const queryClient = new QueryClient();
const WagmiProvider: React.FC = ({ children }) => { const [jwt, setJwt] = React.useState(null);
return ( <wagmiweb3configprovider siweconfig="{{" getnonce:="" async="" (address)=""> (await getNonce(address)).data, createMessage: (props) => { return createSiweMessage({ ...props, statement: "Ant Design Web3" }); }, verifyMessage: async (message, signature) => { const jwt = (await verifyMessage(message, signature)).data; setJwt(jwt); return !!jwt; }, }} chains={[Mainnet]} transports={{ [Mainnet.id]: http(), }} walletConnect={{ projectId: YOUR_WALLET_CONNECT_PROJECT_ID, }} wallets={[ MetaMask(), WalletConnect(), TokenPocket({ group: "Popular", }), OkxWallet(), ]} queryClient={queryClient} > {children} ); };
export default WagmiProvider;
然後添加連接錢包按鈕,這樣就在前端添加了連接入口。至此已經接入了 SIWE,步驟非常簡單。
接下來定義連接按鈕,實現連接錢包和籤名:
javascript "use client"; import type { Account } from "@ant-design/web3"; import { ConnectButton, Connector } from "@ant-design/web3"; import { Flex, Space } from "antd"; import React from "react"; import { JwtProvider } from "./JwtProvider";
export default function App() { const jwt = React.useContext(JwtProvider);
const renderSignBtnText = ( defaultDom: React.ReactNode, account?: Account ) => { const { address } = account ?? {}; const ellipsisAddress = address ? ${address.slice(0, 6)}...${address.slice(-6)} : ""; return Sign in as ${ellipsisAddress}; };
return ( <>
這樣就實現了一個最簡單的 SIWE 登入框架。
接口實現
SIWE 需要一些接口來幫助後端校驗用戶身分。現在我們來簡單實現一下。
Nonce
Nonce 用於讓錢包每次籤名時生成的內容變化,提高籤名可靠性。Nonce 生成需要與用戶傳入的 address 關聯,提高驗證準確性。
Nonce 實現非常直接,首先生成隨機字符串(由字母和數字組成),然後將 nonce 和 address 建立聯繫:
javascript import { randomBytes } from "crypto"; import { addressMap } from "../cache";
export async function GET(request: Request) { const { searchParams } = new URL(request.url); const address = searchParams.get("address");
if (!address) { throw new Error("Invalid address"); } const nonce = randomBytes(16).toString("hex"); addressMap.set(address, nonce); return Response.json({ data: nonce, }); }
signMessage
signMessage 用於籤名內容,這部分功能通常由錢包插件完成,我們一般不需要配置,只需指定方法即可。本 demo 中使用 Wagmi 的籤名方法。
verifyMessage
用戶籤名後,需將籤名前內容和籤名一同發給後端校驗。後端從籤名中解析出對應內容進行比較,一致則表示驗證通過。
此外,還需對籤名內容進行安全性校驗,如籤名內容中的 Nonce 值是否與派發給用戶的一致等。驗證通過後,需返回用戶 JWT 用於後續權限校驗,示例代碼如下:
javascript import { createPublicClient, http } from "viem"; import { mainnet } from "viem/chains"; import jwt from "jsonwebtoken"; import { parseSiweMessage } from "viem/siwe"; import { addressMap } from "../cache";
const JWT_SECRET = "your-secret-key"; // 請使用更安全的密鑰,並添加對應的過期校驗等
const publicClient = createPublicClient({ chain: mainnet, transport: http(), });
export async function POST(request: Request) { const { signature, message } = await request.json();
const { nonce, address = "0x" } = parseSiweMessage(message); console.log("nonce", nonce, address, addressMap);
// 校驗 nonce 值是否一致 if (!nonce || nonce !== addressMap.get(address)) { throw new Error("Invalid nonce"); }
// 校驗籤名內容 const valid = await publicClient.verifySiweMessage({ message, address, signature, });
if (!valid) { throw new Error("Invalid signature"); }
// 生成 jwt 並返回 const token = jwt.sign({ address }, JWT_SECRET, { expiresIn: "1h" }); return Response.json({ data: token, }); }
至此,一個基本實現 SIWE 登入的 Dapp 就開發完成了。
優化建議
使用默認 RPC 節點進行 SIWE 登入驗證可能需要近 30 秒,因此強烈建議使用專門的節點服務來提升接口響應時間。本文使用 ZAN 的節點服務,可以從 ZAN 節點服務控制臺獲取對應 RPC 連接。
獲取以太坊主網的 HTTPS RPC 連接後,在代碼中替換 publicClient 的默認 RPC:
javascript const publicClient = createPublicClient({ chain: mainnet, transport: http('), //獲取到的 ZAN 節點服務 RPC });
替換後,驗證時間將顯著減少,接口速度明顯加快。