메인 콘텐츠로 건너뛰기
이 짧은 시리즈에서는 Injective 위에 dApp을 구축하는 것이 얼마나 쉬운지 보여드리겠습니다. 모든 사람이 참조하고 Injective 위에 구축하는 데 사용할 수 있는 오픈소스 dApp이 있습니다. Next, Nuxt 및 Vanilla Js에 대한 예제가 있습니다. 처음부터 시작하려는 사람들에게는 여기가 시작하기에 적합한 곳입니다. 이 예제에서는 injective-ts 모듈을 사용하여 Injective Chain에 배포된 예제 스마트 컨트랙트와 연결하고 상호 작용을 구현합니다. 시리즈에는 다음이 포함됩니다:
  • API 클라이언트 및 환경 설정,
  • Chain 및 Indexer API에 연결,
  • 사용자 지갑에 연결하고 주소 가져오기,
  • 스마트 컨트랙트 쿼리 (이 경우 스마트 컨트랙트의 현재 카운트 가져오기),
  • 컨트랙트 상태 수정 (이 경우 카운트를 1씩 증가시키거나 특정 값으로 설정),

설정

먼저 원하는 UI 프레임워크를 구성합니다. 구성에 대한 자세한 내용은 여기에서 찾을 수 있습니다. dex를 시작하려면 API 클라이언트와 환경을 설정해야 합니다. DEX를 구축하기 위해 Injective Chain과 Indexer API 모두에서 데이터를 쿼리할 것입니다. 이 예제에서는 기존 테스트넷 환경을 사용할 것입니다. 먼저 데이터를 쿼리하는 데 필요한 클래스 중 일부를 설정해 보겠습니다. 스마트 컨트랙트와 상호 작용하기 위해 @injectivelabs/sdk-tsChainGrpcWasmApi를 사용할 것입니다. 또한 사용할 네트워크 엔드포인트(메인넷 또는 테스트넷)가 필요하며, 이는 @injectivelabs/networks에서 찾을 수 있습니다. 예시:
//파일명: services.ts
import { Network, getNetworkEndpoints } from "@injectivelabs/networks";
import { ChainGrpcWasmApi } from "@injectivelabs/sdk-ts/client/wasm";

export const NETWORK = Network.Testnet;
export const ENDPOINTS = getNetworkEndpoints(NETWORK);

export const chainGrpcWasmApi = new ChainGrpcWasmApi(ENDPOINTS.grpc);
그런 다음 사용자가 DEX에 연결하고 트랜잭션에 서명을 시작할 수 있도록 지갑 연결도 설정해야 합니다. 이를 위해 사용자가 다양한 지갑 제공자와 연결하고 Injective에서 트랜잭션에 서명하는 데 사용할 수 있게 해주는 @injectivelabs/wallet-strategy 패키지를 사용할 것입니다. @injectivelabs/wallet-strategy의 주요 목적은 개발자에게 Injective에서 다양한 지갑 구현을 가질 수 있는 방법을 제공하는 것입니다. 이러한 모든 지갑 구현은 동일한 ConcreteStrategy 인터페이스를 노출하므로 사용자는 특정 지갑에 대한 기본 구현을 알 필요 없이 이러한 메서드를 사용할 수 있습니다. 시작하려면 다양한 지갑을 기본적으로 사용할 수 있는 기능을 제공하는 WalletStrategy 클래스의 인스턴스를 만들어야 합니다. walletStrategy 인스턴스에서 setWallet 메서드를 사용하여 사용되는 현재 지갑을 전환할 수 있습니다 (참고: setWallet은 비동기이며 await이 필요합니다). 기본값은 Metamask입니다.
// 파일명: wallet.ts
import { ChainId, EvmChainId } from "@injectivelabs/ts-types";
import { WalletStrategy } from "@injectivelabs/wallet-strategy";

const chainId = ChainId.Testnet; // Injective 테스트넷 Chain ID
const evmChainId = EvmChainId.TestnetEvm; // Injective Evm 테스트넷 Chain ID

export const evmRpcEndpoint = `https://eth-sepolia.g.alchemy.com/v2/${process.env.APP_EVM_RPC_KEY}`;

export const walletStrategy = new WalletStrategy({
  chainId,
  evmOptions: {
    evmChainId,
    rpcUrl: evmRpcEndpoint,
  },
});
Ethereum 네이티브 지갑을 사용하지 않으려면 WalletStrategy 생성자 내에서 evmOptions를 생략하세요. 마지막으로 Injective에서 전체 트랜잭션 흐름(준비 + 서명 + 브로드캐스트)을 수행하기 위해 MsgBroadcaster 클래스를 사용할 것입니다.
import { Network } from "@injectivelabs/networks";
export const NETWORK = Network.Testnet;

export const msgBroadcastClient = new MsgBroadcaster({
  walletStrategy,
  network: NETWORK,
});

사용자 지갑에 연결

WalletStrategy를 사용하여 사용자의 지갑과의 연결을 처리하므로 사용자 주소 가져오기, 트랜잭션 서명/브로드캐스트 등과 같은 일부 사용 사례를 처리하기 위해 해당 메서드를 사용할 수 있습니다. 지갑 전략에 대해 자세히 알아보려면 WalletStrategy가 제공하는 문서 인터페이스와 메서드를 탐색할 수 있습니다. 참고: setWallet 메서드를 사용하여 WalletStrategy 내에서 “활성” 지갑을 전환할 수 있습니다 (비동기이며 await이 필요함).
// 파일명: WalletConnection.ts
import {
  WalletException,
  UnspecifiedErrorCode,
  ErrorType,
} from "@injectivelabs/exceptions";
import { Wallet } from "@injectivelabs/wallet-base";
import { walletStrategy } from "./Wallet.ts";

export const getAddresses = async (wallet: Wallet): Promise<string[]> => {
  await walletStrategy.setWallet(wallet);

  const addresses = await walletStrategy.getAddresses();

  if (addresses.length === 0) {
    throw new WalletException(
      new Error("이 지갑에 연결된 주소가 없습니다."),
      {
        code: UnspecifiedErrorCode,
        type: ErrorType.WalletError,
      }
    );
  }

  if (!addresses.every((address) => !!address)) {
    throw new WalletException(
      new Error("이 지갑에 연결된 주소가 없습니다."),
      {
        code: UnspecifiedErrorCode,
        type: ErrorType.WalletError,
      }
    );
  }

  // Ethereum 네이티브 지갑을 사용하는 경우 'addresses'는 hex 주소입니다
  // Cosmos 네이티브 지갑을 사용하는 경우 'addresses'는 bech32 injective 주소입니다,
  return addresses;
};

쿼리

초기 설정이 완료되면 이전에 만든 chainGrpcWasmApi 서비스를 사용하여 스마트 컨트랙트를 쿼리하고 현재 카운트를 가져오고 스마트 컨트랙트에서 get_count를 호출하는 방법을 살펴보겠습니다.
function getCount() {
  const response = (await chainGrpcWasmApi.fetchSmartContractState(
    COUNTER_CONTRACT_ADDRESS, // 컨트랙트 주소
    toBase64({ get_count: {} }) // 쿼리를 Base64로 변환해야 합니다
  )) as { data: string };

  const { count } = fromBase64(response.data) as { count: number }; // 응답을 Base64에서 변환해야 합니다

  return count; // 현재 카운터 값을 반환합니다.
}
이러한 함수(getCount 또는 우리가 만드는 다른 함수)가 있으면 애플리케이션의 어디에서나 호출할 수 있습니다 (일반적으로 Nuxt의 Pinia 또는 React의 Context 제공자와 같은 중앙 집중식 상태 관리 서비스).

상태 수정

다음으로 count 상태를 수정합니다. 이전에 만든 Broadcast Client@injectivelabs/sdk-tsMsgExecuteContractCompat를 사용하여 체인에 메시지를 보내 이를 수행할 수 있습니다. 이 예제에서 사용하는 스마트 컨트랙트에는 상태를 변경하는 2가지 메서드가 있습니다:
  • increment
  • reset
increment는 카운트를 1씩 증가시키고 reset은 카운트를 주어진 값으로 설정합니다. reset은 스마트 컨트랙트의 생성자인 경우에만 호출할 수 있습니다. 이러한 함수를 호출하면 지갑이 열려 메시지/트랜잭션에 서명하고 브로드캐스트합니다. 먼저 카운트를 증가시키는 방법을 살펴보겠습니다.
// 메시지 준비

const msg = MsgExecuteContractCompat.fromJSON({
  contractAddress: COUNTER_CONTRACT_ADDRESS,
  sender: injectiveAddress,
  msg: {
    increment: {}, // 메서드에 매개변수가 없으면 빈 객체를 전달합니다
  },
});

// 메시지 서명 및 브로드캐스트

const response = await msgBroadcastClient.broadcast({
  msgs: msg, // 배열을 사용하여 여러 메시지를 전달할 수 있습니다. 예: [msg1,msg2]
  injectiveAddress: injectiveAddress,
});

console.log(response);
이제 카운터를 특정 값으로 설정하는 방법의 예를 살펴보겠습니다. 이 스마트 컨트랙트에서 카운트는 스마트 컨트랙트의 생성자만 특정 값으로 설정할 수 있습니다.
// 메시지 준비

const msg = MsgExecuteContractCompat.fromJSON({
  contractAddress: COUNTER_CONTRACT_ADDRESS,
  sender: injectiveAddress,
  msg: {
    reset: {
      count: parseInt(number, 10), // 입력에서 항상 문자열이 오기 때문에 여기서 number 변수를 파싱하고 대신 숫자를 전달해야 합니다.
    },
  },
});

// 메시지 서명 및 브로드캐스트

const response = await msgBroadcastClient.broadcast({
  msgs: msg,
  injectiveAddress: injectiveAddress,
});

console.log(response);

전체 예제

이제 Vanilla JS에서 전체 예제를 살펴보겠습니다 (Nuxt 및 Next와 같은 특정 프레임워크에 대한 예제는 여기에서 찾을 수 있습니다)
import { Web3Exception } from "@injectivelabs/exceptions"
import { WalletStrategy } from "@injectivelabs/wallet-strategy"
import { ChainGrpcWasmApi } from "@injectivelabs/sdk-ts/client/wasm"
import { Network, getNetworkEndpoints } from "@injectivelabs/networks"
import { getInjectiveAddress } from "@injectivelabs/sdk-ts/utils"

const chainId = ChainId.Testnet // Injective 테스트넷 Chain ID
const evmChainId = EvmChainId.TestnetEvm // Injective Evm 테스트넷 Chain ID

export const evmRpcEndpoint = `https://eth-sepolia.g.alchemy.com/v2/${process.env.APP_EVM_RPC_KEY}`

const NETWORK = Network.Testnet
const ENDPOINTS = getNetworkEndpoints(NETWORK)

const chainGrpcWasmApi = new ChainGrpcWasmApi(ENDPOINTS.grpc)

export const walletStrategy = new WalletStrategy({
  chainId,
  evmOptions: {
    evmChainId,
    rpcUrl: evmRpcEndpoint,
  },
})

export const getAddresses = async (): Promise<string[]> => {
  const addresses = await walletStrategy.getAddresses()

  if (addresses.length === 0) {
    throw new Web3Exception(
      new Error("이 지갑에 연결된 주소가 없습니다.")
    )
  }

  return addresses
}

const msgBroadcastClient = new MsgBroadcaster({
  walletStrategy,
  network: NETWORK,
})

const [address] = await getAddresses()
const injectiveAddress = getInjectiveAddress(getInjectiveAddress)

async function fetchCount() {
  const response = (await chainGrpcWasmApi.fetchSmartContractState(
    COUNTER_CONTRACT_ADDRESS, // 컨트랙트 주소
      toBase64({ get_count: {} }) // 쿼리를 Base64로 변환해야 합니다
    )) as { data: string }

  const { count } = fromBase64(response.data) as { count: number } // 응답을 Base64에서 변환해야 합니다

  console.log(count)
}

async function increment(){
    const msg = MsgExecuteContractCompat.fromJSON({
    contractAddress: COUNTER_CONTRACT_ADDRESS,
    sender: injectiveAddress,
    msg: {
        increment: {},
        },
    })

    // 메시지 서명 및 브로드캐스트

    await msgBroadcastClient.broadcast({
        msgs: msg,
        injectiveAddress: injectiveAddress,
    })
}

async function main() {
    await fetchCount() // 로그: {count: 5}
    await increment() // 트랜잭션에 서명하고 브로드캐스트하기 위해 지갑이 열립니다
    await fetchCount() // 이제 카운트가 6입니다. 로그: {count: 6}
}

main()

마무리

남은 것은 위에서 설명한 비즈니스 로직을 중심으로 멋진 UI를 구축하는 것입니다 :)