跳至內容

CoinChoice Snap:用任意幣種補充 Gas 費

ETHDenver 黑客松獲獎者

閱讀時間:11 分鐘
CoinChoice Snap:用任意幣種補充 Gas 費

MetaMask Snaps 是讓 MetaMask 成為全球最具擴展性錢包的發展藍圖。身為開發者,您可以透過全新方式將您的功能與 API 帶入 MetaMask。Web3 開發者是這項成長的核心,本系列旨在展示當今正在構建的創新 MetaMask Snaps。

CoinChoice Snap

Snap 儲存庫:https://github.com/coinchoice/coinchoice/

為什麼要開發它?

CoinChoice 的誕生,源自一個雖然瑣碎卻極為普遍的問題:Web3 使用者可能擁有持有有價代幣的錢包,卻沒有足夠的 gas 費用代幣來執行任何交易。

這個問題對於流動性分散的使用者尤為突出。管理多個錢包、並在不同區塊鏈生態系統中把握機會的使用者,往往需要將 gas 代幣分散存放於各個錢包中,將原本用於投資的資金預留下來,以便處理數位資產和交易。除了 gas 管理問題之外,當代幣被轉移到使用者尚未持有 gas 的網路時,使用者必須先取得 gas 才能進行任何後續操作。這通常需要使用者前往中心化交易所取得並提領 gas,從而承擔 CEX 的提領手續費。

從根本上來說,這樣的體驗存在缺陷。使用者存取 Web3 不應該依賴中心化交易所。使用者不應該為管理 gas 而煩惱,而應該能夠直接與去中心化系統互動。這並不是說不應該收取 gas 費用。Gas 的存在有其重要意義——它能獎勵網路的運營者,同時防止垃圾訊息與 DDoS 攻擊。Gas 只需要更加靈活。如果 dApp 或遊戲開發者擁有原生代幣,該代幣就應該可以作為支付 gas 的貨幣。

基於這樣的理念,CoinChoice 應運而生。

CoinChoice 運行於使用者的瀏覽器中,為每筆交易提供以使用者所選貨幣管理 gas 的能力。如果使用者偏好持有 USDC 而非 ETH,則可以直接用 USDC 支付 gas。

CoinChoice 的 MetaMask Snap 進一步提升了使用體驗。使用者可以直接在 MetaMask 瀏覽器擴充功能中,以所選的 gas 貨幣兌換 ETH 進行儲值。這讓 MetaMask 成為取代前往中心化交易所的解決方案,整合並簡化了 Web3 體驗,同時幫助 Web3 新手在不被日益增多的服務與平台所淹沒的情況下,逐步熟悉各類系統。

請帶我們了解技術實作細節?

CoinChoice 是一款 MetaMask 與瀏覽器擴充功能,讓您可以自由選擇用哪種代幣支付 gas。

套件

  • backend:一個 NestJS 網頁伺服器,負責——對 Tenderly API 和 0x Quote API 模擬交易,以估算所選貨幣的 gas 費用

  • 接收並在客戶端的 MetaMask Snap 與瀏覽器擴充功能之間傳遞訊息

  • 接收 meta-tx 並轉發至中繼合約

  • browser-extension:由 Plasmo Framework 驅動的瀏覽器擴充功能——攔截發送至 MetaMask 的 RPC 請求,以注入兌換 ETH 的交換操作來支付 gas

  • 提供可選擇 Gas 代幣的彈出視窗

  • 提供由 Cypher Onboarding SDK 驅動的代幣管理彈出視窗

  • 所有資料均提交至後端服務,由其管理區塊鏈互動與模擬

  • contracts:驅動中繼 meta-transactions 的智能合約,以促成 0x Protocol 上的交換操作

  • frpc:用於利用 Fluence Network 的 fRPC Gateway 的套件

  • meta-mask:包含 React.js 網站及相關 MetaMask Snap 的套件——Snap 負責直接在 MetaMask 介面中提供 ETH 兌換功能,顯示「Need ETH?」訊息

  • Snap 的運作方式是向後端發出請求,將訊息轉發至客戶端的瀏覽器擴充功能,實現 MetaMask Snap 啟動瀏覽器擴充功能的體驗

  • 相關網站用於啟用 MetaMask Snap 安裝

技術架構

CoinChoice 是一款瀏覽器擴充功能,為每筆交易提供以所選貨幣儲值 gas 的機會。

瀏覽器擴充功能的設計是在每筆交易前提示使用者以所選貨幣支付 gas。

若使用者接受,系統會捕獲一筆 meta-transaction,將估算數量的所選代幣轉換為 ETH。

此 meta-transaction 會被轉發至後端服務,再由後端轉發至中繼智能合約並執行。

meta-transaction 完成後,由於已存入適當數量的 gas,使用者的原始交易便會被提示並可執行。

MetaMask Snap 的設計是將原本只能在 MetaMask 擴充功能內處理的交易轉發至 CoinChoice 擴充功能。其運作方式如下:

  1. 將包含交易詳情的請求提交至後端服務

  2. 後端透過 websocket 連線將交易資料轉發至瀏覽器擴充功能

  3. 瀏覽器擴充功能收到此 websocket 訊息後,啟動以所選貨幣自動估算並儲值 gas 的流程

後端作為訊息匯流排

CoinChoice 採用後端作為兩個前端元件之間的訊息匯流排,這是因為 MetaMask Snap 具有沙盒隔離的特性。

Snap 的程式碼隔離機制阻止了與位於 MetaMask 擴充功能外部情境中的前端元件進行通訊。

然而,Snap 可以發起伺服器端請求,因此可以使用能夠管理使用者 session 的後端服務,在不同的前端情境之間傳遞訊息。

這一點可以透過我們的原始碼加以說明。

在 MetaMask Snap 中,我們向伺服器提交請求。

參見 https://github.com/Coinchoice/coinchoice/blob/main/packages/meta-mask/packages/snap/src/index.ts#L12

import { OnTransactionHandler } from '@metamask/snaps-types';
import { heading, panel, text } from '@metamask/snaps-ui';

export interface JsonRpcRequest {
    id: string | undefined;
    jsonrpc: '2.0';
    method: string;
    params?: Array<any>;
}

const onTriggerAPI = async (transaction: JsonRpcRequest) => {
    await fetch(`https://api.coinchoice.link/metamask`, {
            method: 'POST',
            body: JSON.stringify(transaction),
            headers: { 'Content-Type': 'application/json' },
        })
    .then((res) => {
        if (!res.ok) {
            throw new Error('Bad response from server');
        }
        return res.json();
    })
    .then((json) => console.log(json))
    .catch((err) => console.error(err));
};

// Handle outgoing transactions
export const onTransaction: OnTransactionHandler = async ({ transaction }) => {
    // trigger browser extension for swap
    const response = await onTriggerAPI(transaction);
    // this is shown if no transaction is triggered
    if (typeof transaction.data === 'string' && transaction.data !== '0x') {
        return {
            content: panel([
                heading('Need ETH for gas?'),
                text('Receive ETH gasless for any currency with Coinchoice!'),
            ]),
        };
    }

    const currentGasPrice = await ethereum.request<string>({
        method: 'eth_gasPrice',
    });

    const transactionGas = parseInt(transaction.gas as string, 16);
    const currentGasPriceInWei = parseInt(currentGasPrice ?? '', 16);
    const maxFeePerGasInWei = parseInt(transaction.maxFeePerGas as string, 16);
    const maxPriorityFeePerGasInWei = parseInt(transaction.maxPriorityFeePerGas as string, 16);

    const gasFees = Math.min(
        maxFeePerGasInWei * transactionGas,
        (currentGasPriceInWei + maxPriorityFeePerGasInWei) * transactionGas
    );

    const transactionValueInWei = parseInt(transaction.value as string, 16);
    const gasFeesPercentage = (gasFees / (gasFees + transactionValueInWei)) * 100;

    return {
        content: panel([
            heading('Need ETH for gas?'),
            text(
                `As setup, you are paying **${gasFeesPercentage.toFixed(
                2
                )}%** in gas fees for this transaction.`
            ),
            text(`Also: **${response}**`),
        ]),
    };
};
複製

接著,在伺服器端,我們使用收到的交易 payload 中的 from 參數來識別錢包及其關聯的 session 識別碼。

參見: https://github.com/Coinchoice/coinchoice/blob/main/packages/backend/src/app.controller.ts#LL36-L49C3

@Post('metamask')
async metamask(@Body() data: any) {
    const address = data.from;
    const wallet = await this.walletService.findOne(address);
    if (wallet) {
        console.log(`clientId for ${address} is: ${wallet.clientId}`);
        if (wallet.clientId) {
            this.appGateway.server.to(wallet.clientId).emit('onMetamask', {
                msg: data,
                content: 'Hello from the backend!',
            });
        } else throw new NotFoundException('ClientId not found for this wallet');
    } else throw new NotFoundException('Wallet not found');
}
複製

最後,在瀏覽器擴充功能中,我們透過 websocket 連線連接至後端服務,連線本身即建立了使用者的 session 識別碼。參見: https://github.com/Coinchoice/coinchoice/blob/main/packages/browser-extension/src/contents/main.ts#L107

const socket = io(API_HOST);
socket.on('connect', function () {
    console.log('CS SOCKET: Connected');
});
socket.on('events', function (data) {
    console.log('CS SOCKET: event', data);
});
socket.on('onMessage', async function (data) {
    console.log('CS SOCKET: onMessage', data);
    if (data.msg.startsWith('Hello client')) {
        const [_, clientId] = data.msg.split('#');
        wallet.clientId = clientId;
        facade.setWallet(wallet);
        try {
            await busPromise('connect-wallet', { wallet });
        } catch (e) {
            console.log('CS SOCKET ERROR: onMessage - connect wallet');
            console.error(e);
        }
    }
});
socket.on('onMetamask', async function (data) {
    console.log('CS SOCKET: onMetamask', data);
    // On Metamask, open the Notification.
    const { msg: tx }: { msg: TxRequest } = data;
    // Cancel the original tx

    await facade.waitForSignature({ method: 'need_eth', params: [tx] });
});
複製

在此程式碼片段中,我們可以看到透過 onMetamask 訊息接收到的交易資料如何啟動簽名提示(meta-transaction)。

用於安裝 Snap 的網站

要安裝 MetaMask Snap,網站必須呼叫相應的 JSON RPC 端點。

/**
 * Connect a snap to MetaMask.
 *
 * @param snapId - The ID of the snap.
 * @param params - The params to pass with the snap to connect.
 */
export const connectSnap = async (
  snapId: string = defaultSnapOrigin,
  params: Record<'version' | string, unknown> = {},
) => {
  await window.ethereum.request({
    method: 'wallet_requestSnaps',
    params: {
      [snapId]: params,
    },
  });
};
複製

CoinChoice 用於安裝 MetaMask Snap 的網站託管於 https://cchoice.xyz/,其靈感來自 MetaMask 團隊的儲存庫:https://github.com/ziad-saab/snappy-recovery

開始使用 CoinChoice

若要開始使用,您可以透過以下指南安裝已啟用測試網的 CoinChoice 版本:https://app.coinchoice.link/

如果繼續實作,下一步是什麼?

CoinChoice 已針對 Goerli 以太坊測試網進行實作與測試,雖然目前仍屬實驗性質。

假設各方激勵機制一致,足以支持 CoinChoice 的持續開發,下一步將是讓 CoinChoice 支援所有主要的 EVM 相容區塊鏈,並盡可能將瀏覽器擴充功能推廣至更多使用者手中。

曝光度與推廣一直是挑戰,因此需要投入社群發展,並與 Web3 平台(尤其是跨鏈平台)建立合作關係,以緩解 gas 需求方面的顧慮。

一旦應用程式投入使用並收集到終端使用者的回饋,便可確定並實施錯誤修復及其他改進,進一步提升終端使用者體驗。

至於未來的發展方向,早期曾考慮但因呼叫者情境相關問題而未實作的帳戶抽象化(Account Abstraction)功能,可能會重新納入考量。CoinChoice 的核心功能也可以模組化,讓 dApp 開發者能夠直接將 gas 管理嵌入其應用程式中。

能否介紹一下您自己和您的團隊?

CoinChoice 團隊相當多元,成員來自截然不同的背景,但共同懷抱同一個目標——為去中心化的未來打造出色的軟體。

我是 Ryan,負責創建 CoinChoice 瀏覽器擴充功能並支援解決方案架構的工程師。我透過 EthDenver 的 Discord 尋找團隊成員,所有成員也是在丹佛首次實際碰面。我們每個人都有獨特的技能,讓我們能夠非常有效地協作。面對分歧與問題時,我們都直接正視,功過與成就由所有成員共同承擔。透過這次黑客松的經歷,團隊可以自信地說,共事是一段愉快的旅程。如果這次合作衍生出任何機會,我們都願意繼續相互支持。

就團隊經驗而言,Achim 是橫跨經濟學與數學領域的工程師最佳典範。Achim 開發了一個名為 1Delta.io 的 DeFi 協議,正在突破槓桿數位資產管理的可能性邊界。這段經驗對於以生產級安全標準開發和部署 CoinChoice 的智能合約至關重要。

Andre 擁有 Web2 背景,因此是開發和管理伺服器端流程的最佳人選,這些流程對於管理和執行 meta-transactions 及其他應用程式資料不可或缺。Andrew 領導著巴西的 Web2 代理商 Web Eleven。

Christie 在非技術層面支援團隊,負責簡報製作、品牌塑造、內容創作和專案管理。Christie 領導著澳洲的 Web2 代理商 Web Doodle。

至於我自己,我擁有解決方案架構與工程背景。我主導一家名為 Usher.so 的 Web3 新創公司,我們開發了一個合作夥伴計畫平台,並正在探索新的去中心化基礎設施來支撐該平台。在負責前端開發與解決方案架構的同時,我也將協調專案與團隊的經驗帶入了 EthDenver 黑客松。

您認為 MetaMask Snaps 為 Web3 領域帶來了哪些機遇?

MetaMask 正將自己定位為使用者進入 Web3 時最早互動的應用程式之一。透過開放使用者體驗的擴展性,MetaMask 讓開發者能夠在早期且熟悉的接觸點引入其價值主張。一旦 Web3 入口(尤其是面向下一代 Web3 使用者的入口)出現整合趨勢,這一點將變得更加重要。這類似於我們已經熟悉的網際網路入口整合現象——社群網路或搜尋引擎等面向終端使用者的應用程式,已成為許多網路使用者的第一個互動入口。

MetaMask Snaps 能夠實現更具情境感知的體驗。一個可能的應用場景是:Snap 根據 MetaMask 錢包正在互動的 dApp 而表現出不同的行為。透過讓開發者在 Web3 的覆蓋層使用者介面上進行構建,可以融入更多有趣的創新,從根本上改變與 Web3 互動的方式。與只為特定協議提供 UI 的 dApp 不同,Snap 開發者可以改變所有協議的互動方式,因為創新就存在於支援這些互動的錢包之中。

最後,旨在提升交易透明度、簡化 Web3 互動並為終端使用者提供額外安全保障的 MetaMask Snaps,將從根本上讓 Web3 對廣大使用者而言更加安全、更具可行性。

對於有意嘗試 MetaMask Snaps 的開發者,您有什麼建議?

參閱實作 MetaMask Snaps 的原始程式碼,以深入了解這些新功能的運作方式。MetaMask Snaps 團隊在公開完善的程式碼方面做得非常出色,這些程式碼展示了 MetaMask Snaps 在實際專案中的應用。雖然文件提供了功能概覽,但原始程式碼才能說明這些功能是如何實作和運用的。

在早期開發階段,請降低對 Snaps Framework 能力的期望。雖然 MetaMask Snaps 最終將實現 MetaMask 的完整擴展性,但 Snaps Framework 中各元件的存取權限將逐步開放。MetaMask 在最大化終端使用者安全性時需要拿捏平衡,因此早期版本中包含的某些功能可能會被重新考量。如果您的解決方案需要更多存取使用者瀏覽器情境的權限,請考慮參考 CoinChoice 的原始程式碼,開發一個由 MetaMask Snap 內的操作觸發的瀏覽器擴充功能。

聯繫 MetaMask 團隊。MetaMask 團隊對於實現擴展性高度投入,並積極尋求回饋與機會,以更好地實現這一價值主張。

使用 MetaMask Snaps 進行開發

開始使用 MetaMask Snaps:

  1. 查閱開發者文件

  2. 安裝 MetaMask Flask

  3. 參閱 MetaMask Snaps 指南

  4. 在 Twitter、GitHub discussions 和 Discord 上與我們保持聯繫

留意我們團隊在您附近舉辦的下一場黑客松!祝 BUIDL 愉快 ⚒️

免責聲明:MetaMask Snaps 通常由 Consensys Software 以外的第三方開發。使用第三方開發的 MetaMask Snaps 須由您自行判斷並承擔風險,且您同意對因此類活動所造成的任何損失或損害負全部責任。Consensys 對第三方開發的 MetaMask Snaps 不作任何明示或暗示的保證,並對第三方開發的 Snaps 免除一切責任。使用區塊鏈相關軟體存在風險,使用 MetaMask Snaps 即表示您完全承擔這些風險。

AI 翻譯。可能包含錯誤。請務必核實資訊。

評價此翻譯

本文作者:

  • MetaMask

    MetaMask,前身為 Consensys Software Inc,是全球最大的自託管金融平台,讓用戶能在單一平台上持有、消費、儲蓄並增值其資產,涵蓋加密貨幣與傳統資產。公司正致力於打造實現上述目標的消費者平台,將支付、儲蓄、投資與數位資產整合於一個無縫的使用體驗中。MetaMask 從全球使用最廣泛的自託管錢包發展而來,讓用戶直接掌控自己的資金與資產,服務範圍遍及約 190 個國家。自 2016 年起,MetaMask 在以太坊的發展歷程中扮演了奠基性的角色。時至今日,MetaMask 位居區塊鏈經濟的核心,致力於構建「開放金融」的作業系統,讓每個人都能全面掌握自己的財務生活。

    閱讀所有文章