콘텐츠로 건너뛰기

CoinChoice Snap: 모든 통화로 가스비 충전

ETHDenver 해커톤 수상작

11분 읽기
CoinChoice Snap: 모든 통화로 가스비 충전

MetaMask Snaps는 MetaMask를 세계에서 가장 확장성 높은 지갑으로 만들기 위한 로드맵입니다. 개발자라면 완전히 새로운 방식으로 자신만의 기능과 API를 MetaMask에 구현할 수 있습니다. Web3 개발자들은 이러한 성장의 핵심이며, 이 시리즈는 현재 개발 중인 혁신적인 MetaMask Snaps를 소개하는 것을 목표로 합니다.

CoinChoice Snap

Snap 저장소: https://github.com/coinchoice/coinchoice/

왜 만들었나요?

CoinChoice는 Web3 사용자라면 누구나 한 번쯤 겪어봤을 작지만 번거로운 문제에서 출발했습니다. 바로 가치 있는 토큰이 담긴 지갑을 보유하고 있음에도 트랜잭션을 실행할 가스 토큰이 없는 상황입니다.

이 문제는 유동성이 분산된 사용자에게 더욱 두드러집니다. 여러 지갑을 관리하며 다양한 블록체인 생태계에서 기회를 탐색하는 사용자는 각 지갑에 가스 토큰을 나눠 보관해야 하며, 이로 인해 투자에 활용할 수 있는 자본이 디지털 자산 관리와 트랜잭션 처리를 위해 묶이게 됩니다. 가스 관리 문제 외에도, 가스를 보유하지 않은 네트워크로 토큰이 전송된 경우 사용자는 추가 작업을 진행하기 전에 먼저 가스를 확보해야 합니다. 일반적으로 이 과정에서 중앙화 거래소(CEX)에 접속해 가스를 구매하고 출금해야 하며, 이에 따른 출금 수수료가 발생합니다.

근본적으로 이 경험은 결함이 있습니다. 사용자가 Web3에 접근하기 위해 중앙화 거래소에 의존해야 할 이유가 없습니다. 사용자는 가스 관리에 신경 쓰는 대신 탈중앙화 시스템과 자유롭게 상호작용할 수 있어야 합니다. 이것이 가스가 필요 없다는 의미는 아닙니다. 가스는 네트워크 운영자에게 보상을 제공하고 스팸 및 DDoS를 방지하는 훌륭한 역할을 합니다. 다만 가스는 보다 유연해져야 합니다. dApp이나 게임 개발자가 자체 토큰을 보유하고 있다면, 해당 토큰이 가스 지불 수단으로 충분히 활용될 수 있어야 합니다.

이러한 철학을 바탕으로 CoinChoice가 개발되었습니다.

CoinChoice는 사용자의 브라우저 내에서 작동하며, 모든 트랜잭션에 사용자가 원하는 통화로 가스를 관리할 수 있는 기능을 추가합니다. 사용자가 ETH 대신 USDC를 보유하고 싶다면, USDC로 가스를 지불할 수 있습니다.

CoinChoice의 MetaMask Snap은 이 경험을 한층 더 보완합니다. MetaMask 브라우저 확장 프로그램 내에서 직접 사용자가 선택한 가스 통화를 ETH로 교환하여 충전할 수 있습니다. 이를 통해 MetaMask는 중앙화 거래소 방문을 대체하는 솔루션으로 거듭나며, Web3 경험을 통합하고 단순화합니다. 또한 신규 Web3 사용자가 점점 늘어나는 서비스와 플랫폼에 압도되지 않고 일부 시스템에 익숙해질 수 있도록 돕습니다.

기술 구현 방식을 설명해 주세요.

CoinChoice는 가스 지불에 사용할 코인을 선택할 수 있는 MetaMask 및 브라우저 확장 프로그램입니다.

패키지

  • backend: NestJS 웹 서버로 다음 역할을 담당합니다. Tenderly API 및 0x Quote API를 통한 트랜잭션 시뮬레이션으로 선택한 통화 기준 가스 비용 산출

  • 클라이언트의 MetaMask Snap과 브라우저 확장 프로그램 간 메시지 수신 및 중계

  • 릴레이어 컨트랙트로 전달할 meta-tx 수신

  • browser-extension: Plasmo 프레임워크 기반의 브라우저 확장 프로그램으로 다음 기능을 제공합니다. 가스 자금 조달을 위한 ETH 스왑을 주입하기 위해 MetaMask로의 RPC 요청 인터셉트

  • 가스 코인 선택 팝업 제공

  • Cypher Onboarding SDK 기반의 토큰 관리 팝업 제공

  • 모든 데이터는 블록체인 상호작용 및 시뮬레이션이 관리되는 백엔드 서비스로 전송

  • contracts: 0x 프로토콜에서 스왑을 처리하기 위한 meta-transaction 릴레이를 지원하는 스마트 컨트랙트

  • frpc: Fluence 네트워크의 fRPC 게이트웨이를 활용하기 위한 패키지

  • meta-mask: React.js 사이트와 관련 MetaMask Snap이 포함된 패키지. Snap은 MetaMask 인터페이스 내에서 직접 "ETH가 필요하신가요?"라는 메시지를 표시하여 ETH 스왑을 제공하는 역할을 합니다.

  • Snap은 백엔드에 요청을 보내 클라이언트의 브라우저 확장 프로그램으로 메시지를 전달하는 방식으로 작동하며, MetaMask Snap이 브라우저 확장 프로그램을 시작할 수 있는 경험을 제공합니다.

  • 관련 사이트는 MetaMask Snap 설치를 지원하는 데 사용됩니다.

기술 설정

CoinChoice는 트랜잭션마다 선택한 통화로 가스를 충전할 수 있는 기회를 제공하는 브라우저 확장 프로그램입니다.

브라우저 확장 프로그램은 각 트랜잭션 전에 사용자가 선택한 통화로 가스를 지불하도록 안내하도록 설계되었습니다.

사용자가 수락하면, 선택한 토큰의 예상 금액을 ETH로 전환하는 meta-transaction이 캡처됩니다.

이 meta-transaction은 백엔드 서비스로 전달되고, 백엔드는 이를 릴레이어 스마트 컨트랙트로 전달하여 실행합니다.

meta-transaction이 완료되면, 적절한 가스 금액이 입금되어 사용자의 원래 트랜잭션이 실행 가능한 상태가 됩니다.

MetaMask Snap은 MetaMask 확장 프로그램 내에서만 처리될 수 있는 트랜잭션을 CoinChoice 확장 프로그램으로 전달하도록 설계되었습니다. 이는 다음과 같은 방식으로 이루어집니다.

  1. 트랜잭션 세부 정보를 포함한 요청을 백엔드 서비스에 제출합니다.

  2. 백엔드는 웹소켓 연결을 통해 트랜잭션 데이터를 브라우저 확장 프로그램으로 전달합니다.

  3. 브라우저 확장 프로그램이 이 웹소켓 메시지를 수신하면, 선택한 통화를 자동으로 산출된 가스 금액으로 충전하는 흐름을 시작합니다.

메시지 버스로서의 백엔드

CoinChoice는 MetaMask Snap의 샌드박스 특성으로 인해 두 프론트엔드 컴포넌트 간의 메시지 버스로 백엔드를 활용하는 방식을 채택했습니다.

Snap 코드 격리로 인해 MetaMask 확장 프로그램 외부 컨텍스트에 있는 프론트엔드 컴포넌트와의 통신이 차단됩니다.

그러나 Snap은 서버 측 요청을 처리할 수 있으므로, 사용자 세션을 관리할 수 있는 백엔드 서비스를 통해 프론트엔드 컨텍스트 간에 메시지를 전달할 수 있습니다.

이에 대한 예시는 소스 코드를 통해 확인할 수 있습니다.

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}**`),
        ]),
    };
};
복사

그런 다음 서버 내에서 수신된 트랜잭션 페이로드의 from 파라미터를 사용하여 지갑과 관련 세션 식별자를 확인합니다.

참조: 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');
}
복사

마지막으로, 브라우저 확장 프로그램 내에서 웹소켓 연결을 통해 백엔드 서비스에 연결하며, 연결 자체가 사용자 세션 식별자를 설정합니다. 참조: 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,
    },
  });
};
복사

https://cchoice.xyz/에서 호스팅되는 MetaMask Snap 설치용 CoinChoice 웹사이트는 MetaMask 팀의 저장소(https://github.com/ziad-saab/snappy-recovery)에서 영감을 받았습니다.

CoinChoice 시작하기

시작하려면 https://app.coinchoice.link/에서 제공하는 가이드를 통해 테스트넷 버전의 CoinChoice를 설치할 수 있습니다.

다음 단계는 무엇인가요?

CoinChoice는 Goerli 이더리움 테스트넷을 기준으로 구현 및 테스트되었으며, 아직 실험적인 단계입니다.

CoinChoice의 지속적인 개발을 위한 인센티브가 갖춰진다면, 다음 단계는 모든 주요 EVM 호환 블록체인에서 CoinChoice를 지원하고, 가능한 한 많은 사용자에게 브라우저 확장 프로그램을 보급하는 것입니다.

노출과 배포는 항상 어려운 과제이므로, 커뮤니티 개발과 Web3 플랫폼, 특히 가스 요구 사항에 대한 우려를 해소하고자 하는 크로스체인 플랫폼과의 파트너십에 대한 투자가 필요합니다.

애플리케이션이 사용되고 최종 사용자로부터 피드백이 수집되면, 버그 수정 및 기타 개선 사항을 파악하고 적용하여 최종 사용자 경험을 더욱 향상시킬 수 있습니다.

향후 개발과 관련하여, 호출자 컨텍스트 문제로 인해 구현되지 않았던 Account Abstraction에 대한 초기 검토가 재고될 수 있습니다. 또한 CoinChoice의 핵심 기능을 모듈화하여 dApp 개발자들이 자신의 애플리케이션에 가스 관리 기능을 간편하게 내장할 수 있도록 할 수도 있습니다.

팀에 대해 소개해 주세요.

CoinChoice 팀은 매우 다양한 배경을 가진 구성원들로 이루어져 있지만, 탈중앙화된 미래를 위한 훌륭한 소프트웨어를 만들겠다는 공통된 목표를 공유하고 있습니다.

저는 Ryan으로, CoinChoice 브라우저 확장 프로그램을 개발하고 솔루션 아키텍처를 지원한 엔지니어입니다. EthDenver의 Discord를 통해 팀원을 모집했으며, 덴버에서 모든 팀원이 처음으로 직접 만났습니다. 각자 고유한 역량을 갖추고 있어 매우 효율적으로 협업할 수 있었습니다. 의견 충돌과 문제는 정면으로 해결했으며, 책임과 성과는 모든 팀원이 동등하게 나눴습니다. 해커톤 경험을 통해 팀 모두가 함께 일하는 것이 즐거웠다고 자신 있게 말할 수 있습니다. 이 프로젝트에서 새로운 기회가 생긴다면, 모두가 서로를 지원할 준비가 되어 있습니다.

팀 경험에 대해 말씀드리자면, Achim은 경제학과 수학이 교차하는 분야의 엔지니어로 가장 좋은 예입니다. Achim은 레버리지 디지털 자산 관리의 가능성을 한계까지 끌어올리는 DeFi 프로토콜 1Delta.io를 개발했습니다. 이 경험은 프로덕션 수준의 보안으로 CoinChoice의 스마트 컨트랙트를 개발하고 배포하는 데 매우 중요한 역할을 했습니다.

Andre는 Web2 배경을 가지고 있어 meta-transaction 관리 및 실행과 기타 애플리케이션 데이터 처리에 필수적인 서버 측 프로세스를 개발하고 관리하기에 완벽한 인재였습니다. Andrew는 브라질의 Web2 에이전시 Web Eleven을 이끌고 있습니다.

Christie는 덱 제작, 브랜딩, 콘텐츠, 프로젝트 관리 등 비기술적인 측면에서 팀을 지원했습니다. Christie는 호주의 Web2 에이전시 Web Doodle을 이끌고 있습니다.

저는 솔루션 아키텍처 및 엔지니어링 배경을 가지고 있습니다. 파트너 프로그램 플랫폼을 개발하고 이를 지원하는 새로운 탈중앙화 인프라를 탐색 중인 Web3 스타트업 Usher.so를 이끌고 있습니다. 프론트엔드 개발과 솔루션 아키텍처를 담당하면서, EthDenver 해커톤에서 프로젝트와 팀을 조율한 경험을 살릴 수 있었습니다.

MetaMask Snaps와 Web3 공간에서 열리는 가능성에 대해 어떻게 생각하시나요?

MetaMask는 사용자가 Web3를 처음 접할 때 상호작용하는 첫 번째 애플리케이션 중 하나로 자리매김하고 있습니다. 사용자 경험의 확장성을 지원함으로써, MetaMask는 개발자들이 초기의 친숙한 접점에서 자신의 가치 제안을 소개할 수 있도록 합니다. 이는 특히 차세대 Web3 사용자를 위한 Web3 진입 경로가 통합될수록 더욱 중요해집니다. 이는 우리 모두가 이미 익숙한 인터넷 진입 경로의 통합과 유사합니다. 소셜 네트워크나 검색 엔진과 같은 최종 사용자 대면 애플리케이션이 많은 인터넷 사용자의 첫 번째 상호작용 지점이 된 것처럼 말입니다.

MetaMask Snaps는 더욱 맥락에 맞는 경험을 가능하게 합니다. 예를 들어, MetaMask 지갑이 상호작용하는 dApp에 따라 다르게 작동하는 Snap이 그 결과물이 될 수 있습니다. 개발자들이 Web3 오버레이 사용자 인터페이스를 기반으로 구축할 수 있게 함으로써, Web3와 상호작용하는 방식을 근본적으로 바꾸는 더 흥미로운 발전이 통합될 수 있습니다. 특정 프로토콜을 위한 UI를 제공하는 dApp과 달리, Snap 개발자들은 이러한 상호작용을 가능하게 하는 지갑 내에 혁신이 존재하기 때문에 모든 프로토콜과의 상호작용 방식을 변경할 수 있습니다.

마지막으로, 트랜잭션의 투명성을 높이고, Web3 상호작용을 단순화하며, 최종 사용자에게 추가적인 보안을 제공하는 MetaMask Snaps는 Web3를 더 안전하고 더 많은 사용자가 이용할 수 있는 공간으로 만들 것입니다.

MetaMask Snaps를 시도하려는 개발자들에게 조언을 해주신다면?

MetaMask Snaps를 구현한 원본 소스 코드를 참조하여 이러한 새로운 기능이 어떻게 작동하는지 더 깊이 이해하세요. MetaMask Snaps 팀은 프로젝트 내에서 MetaMask Snaps를 보여주는 잘 개발된 코드를 공개하는 데 훌륭한 작업을 해왔습니다. 문서가 기능에 대한 개요를 제공하는 반면, 소스 코드는 이러한 기능이 어떻게 구현되고 활용되는지를 보여줍니다.

초기 개발 단계에서 Snaps 프레임워크 내에서 가능한 것에 대한 기대치를 낮추세요. MetaMask Snaps가 결국 MetaMask의 완전한 확장성을 지원하게 되겠지만, Snaps 프레임워크 내의 다양한 컴포넌트에 대한 접근은 점진적으로 공개될 것입니다. MetaMask는 최종 사용자 보안을 극대화하면서 균형을 맞춰야 하므로, 초기 빌드에 포함된 일부 기능은 재검토될 수 있습니다. 솔루션이 사용자의 브라우저 컨텍스트에 더 많은 접근이 필요하다면, CoinChoice 소스 코드를 참조하여 MetaMask Snap 내의 작업으로 트리거되는 브라우저 확장 프로그램을 개발하는 것을 고려해 보세요.

MetaMask 팀에 연락하세요. MetaMask 팀은 확장성 구현에 매우 적극적으로 참여하고 있으며, 이 가치 제안을 더 잘 실현하기 위한 피드백과 기회를 찾고 있습니다.

MetaMask Snaps로 빌딩하기

MetaMask Snaps를 시작하려면:

  1. 개발자 문서를 확인하세요.

  2. MetaMask Flask를 설치하세요.

  3. MetaMask Snaps 가이드를 참고하세요.

  4. Twitter, GitHub discussions, Discord에서 저희와 계속 소통하세요.

가까운 지역에서 열리는 다음 해커톤에서 저희 팀을 만나보세요! 즐거운 BUIDLing ⚒️

면책 조항: MetaMask Snaps는 일반적으로 Consensys Software 외의 제3자에 의해 개발됩니다. 제3자가 개발한 MetaMask Snaps의 사용은 전적으로 사용자 본인의 판단과 위험 부담 하에 이루어지며, 그로 인한 손실이나 손해에 대한 책임은 전적으로 사용자에게 있습니다. Consensys는 제3자가 개발한 MetaMask Snaps에 대해 구두 또는 서면을 불문하고 어떠한 명시적 또는 묵시적 보증도 하지 않으며, 제3자가 개발한 Snaps에 대한 모든 책임을 부인합니다. 블록체인 관련 소프트웨어 사용에는 위험이 따르며, MetaMask Snaps 사용 시 해당 위험을 전적으로 감수하게 됩니다.

AI가 번역했습니다. 오류가 있을 수 있습니다. 항상 정보를 확인하시기 바랍니다.

번역 평가하기

이 글의 작성자:

  • MetaMask

    MetaMask(구 Consensys Software Inc)은 세계 최대의 자기 수탁형 금융 플랫폼으로, 암호화폐와 전통 자산 모두에 걸쳐 자산을 보유하고, 사용하고, 저축하고, 불릴 수 있는 단일 공간을 제공합니다. MetaMask는 결제, 저축, 투자, 디지털 자산을 하나의 seamless한 경험으로 통합하는 소비자 플랫폼을 구축하고 있습니다. 세계에서 가장 널리 사용되는 자기 수탁형 지갑에서 성장한 MetaMask는 약 190개국에 걸쳐 사용자가 자신의 자금과 자산을 직접 통제할 수 있도록 합니다. MetaMask는 2016년부터 이더리움의 성장에 핵심적인 역할을 해왔습니다. 오늘날 MetaMask는 블록체인 경제의 중심에 자리하며, 오픈 머니(Open Money)를 위한 운영 체제를 구축하고 사람들이 자신의 금융 생활을 완전히 주도할 수 있도록 하고 있습니다.

    모든 글 읽기