본문으로 건너뛰기

지갑 연결

사용자가 지갑을 선택하고 승인하면 AppKit TypesWalletConnection에서 계정과 네트워크를 바로 사용할 수 있습니다. 이 가이드는 네트워크별 지갑 목록을 만들고, 사용자 클릭에서 EVM 또는 Solana 지갑을 연결하며, 실패 원인에 맞는 다음 행동을 보여 줍니다.

@scope-connect/appkit은 UI 없이 지갑·네트워크 정보와 연결 Promise만 제공하는 SDK입니다. 버튼·지갑 선택창 같은 UI가 필요하면 @scope-connect/appkit-react를 사용하고(UI 구현 참고), 이 가이드처럼 @scope-connect/appkit을 직접 사용할 때는 화면 상태를 dApp이 관리합니다.

준비 사항

  • AppKit 설정에 따라 init()을 호출한 AppKit 인스턴스
  • 연결 버튼을 실행할 사용자 클릭 이벤트
  • 모바일 SDK 경로를 사용한다면 해당 선택 의존성과 앱 전환 처리

기본 데스크톱 주입형 연결은 Connect 백엔드를 거치지 않지만, AppKit 인스턴스를 만들 때는 공개 clientId가 항상 필요합니다.

1. 연결 메서드 선택

사용자 흐름메서드사용할 때
감지된 기본 주입형 지갑에 바로 연결connect()지갑 선택 화면이 필요 없는 가장 짧은 흐름
사용자가 고른 지갑·체인에 연결selectWallet(walletId, { chainId })EVM과 Solana 지갑 선택 화면
QR·딥링크로 릴레이 지갑 연결pair()Scope Connect 같은 릴레이 전용 지갑

Scope Connect(scope)는 지갑 목록에 포함되지만 selectWallet()으로 연결하지 않습니다. 지갑 목록에서 별도 릴레이 버튼으로 라우팅하고 릴레이로 지갑 연결를 따릅니다.

2. 네트워크별 지갑 목록 만들기

프로젝트 화면에서는 먼저 loadConfig()를 호출한 뒤 getSupportedWallets()getSupportedNetworks()를 사용합니다. 두 메서드는 프로젝트 콘솔에서 활성화한 항목을 반영하며, 설정을 불러오지 못하면 SDK 내장 목록을 사용합니다. 사용자가 네트워크를 먼저 고르게 하면 호환되지 않는 지갑을 화면에서 미리 제외할 수 있습니다.

src/wallet/catalog.ts
import {
type Caip2ChainId,
type SupportWalletInfo,
} from "@scope-connect/appkit";
import { appKit } from "../lib/appkit";

export type WalletChoice = {
id: SupportWalletInfo["walletId"];
label: string;
icon?: string;
method: "selectWallet" | "pair";
};

export function getWalletChoices(chainId: Caip2ChainId): WalletChoice[] {
return appKit
.getSupportedWallets()
.filter((wallet) => {
if (chainId.startsWith("eip155:")) return wallet.eip155 !== undefined;
if (chainId.startsWith("solana:")) return wallet.solana !== undefined;
return false;
})
.map((wallet) => ({
id: wallet.walletId,
label: wallet.displayName,
icon: wallet.icon,
method: wallet.walletId === "scope" ? "pair" : "selectWallet",
}));
}

예를 들어 getWalletChoices("solana:devnet")는 Solana devnet을 지원하는 프로젝트 지갑을 반환합니다. 네트워크 선택 값은 임의로 만들지 않고 getSupportedNetworks()가 반환하는 chainId에서 가져옵니다. 프로젝트 설정과 무관한 SDK 내장 목록만 조회할 때는 AppKit 선택적 APIgetWalletsForNetwork()를 사용합니다.

src/wallet/networks.ts
import { type Caip2ChainId } from "@scope-connect/appkit";
import { appKit } from "../lib/appkit";

export type NetworkChoice = {
chainId: Caip2ChainId;
label: string;
isTestnet: boolean;
};

export function getNetworkChoices(): NetworkChoice[] {
return appKit.getSupportedNetworks().map((network) => ({
chainId: network.chainId,
label: network.displayName,
isTestnet: network.isTestnet,
}));
}

화면을 구성하기 전에 await appKit.loadConfig()를 호출합니다. 사용자가 고른 NetworkChoice["chainId"]를 그대로 getWalletChoices(chainId)에 넘기면 프로젝트 설정의 네트워크와 지갑을 같은 기준으로 표시할 수 있습니다. getSupportedNetworks()는 네트워크 목록의 복사본을 반환하며 오류를 던지지 않습니다. 반환 배열을 변경해도 SDK 내부 상태에는 영향을 주지 않습니다.

전체 지갑 목록이 필요하면 appKit.getSupportedWallets()를 사용합니다. { mobile: true }는 현재 EVM 모바일 전략 또는 릴레이 지원 여부를 기준으로 필터링하며 Phantom을 포함하지 않습니다. 이를 “모바일에서 사용할 수 있는 모든 지갑”으로 해석하지 말고 지원 지갑과 네트워크의 환경별 제약을 함께 적용합니다. 지갑·네트워크 정보 필드 설명은 AppKit Types에서 확인합니다.

3. 지갑 연결

selectWallet()에는 EVM과 Solana 모두 목표 chainId를 전달할 수 있습니다. SDK는 네임스페이스에 맞는 커넥터를 선택하고, 필요한 경우 지갑에 네트워크 전환을 요청합니다.

src/wallet/connect.ts
import {
AppError,
AppResultCode,
type WalletConnection,
type WalletId,
} from "@scope-connect/appkit";
import { appKit } from "../lib/appkit";

export async function connectSelectedWallet(
walletId: WalletId,
chainId: string,
): Promise<WalletConnection> {
if (walletId === "scope") {
throw new Error("Scope Connect는 릴레이 연결 화면으로 이동해야 합니다.");
}

try {
return await appKit.selectWallet(walletId, { chainId });
} catch (error) {
if (error instanceof AppError) {
switch (error.code) {
case AppResultCode.USER_REJECTED:
throw new Error("사용자가 연결 요청을 취소했습니다.", { cause: error });
case AppResultCode.WALLET_NOT_FOUND:
throw new Error("지갑을 찾지 못했습니다. 설치 또는 앱 실행 상태를 확인하세요.", {
cause: error,
});
case AppResultCode.UNSUPPORTED_CHAIN:
throw new Error("선택한 지갑 또는 SDK가 이 네트워크를 지원하지 않습니다.", {
cause: error,
});
case AppResultCode.INVALID_CONFIG:
throw new Error("AppKit 초기화와 허용 체인 설정을 확인하세요.", {
cause: error,
});
}
}
throw error;
}
}

EVM과 Solana에서 호출 모양은 같습니다. 목표 chainId는 앞 절의 appKit.getSupportedNetworks()에서, walletIdgetWalletChoices(chainId)가 돌려준 목록에서 고릅니다. 둘 다 임의 문자열로 하드코딩하지 않습니다.

const networks = appKit.getSupportedNetworks();

const sepolia = networks.find((network) => network.chainId === "eip155:11155111")!;
const metamask = getWalletChoices(sepolia.chainId).find((wallet) => wallet.id === "metamask")!;
const evmConnection = await connectSelectedWallet(metamask.id, sepolia.chainId);

const solanaDevnet = networks.find((network) => network.chainId === "solana:devnet")!;
const phantom = getWalletChoices(solanaDevnet.chainId).find((wallet) => wallet.id === "phantom")!;
const solanaConnection = await connectSelectedWallet(phantom.id, solanaDevnet.chainId);

Phantom 모바일에서 두 번째 예시를 운영에 사용하려면 dApp이 solanaMobileSession과 영속 저장소를 구현해야 합니다. 설정을 생략하면 오프라인 모의 세션을 사용하므로 실제 지갑 연결 성공으로 취급하지 않습니다.

4. 버튼 상태와 결과 표시

연결 호출은 페이지 로드가 아니라 사용자의 버튼 클릭에서 실행합니다. 지갑 팝업 또는 앱 전환 중에는 중복 클릭을 막고, 성공 후 CAIP 계정과 체인을 화면 상태에 저장합니다.

const networks = appKit.getSupportedNetworks();
const sepolia = networks.find((network) => network.chainId === "eip155:11155111")!;
const metamask = getWalletChoices(sepolia.chainId).find((wallet) => wallet.id === "metamask")!;

const button = document.querySelector<HTMLButtonElement>("#connect-wallet")!;
const status = document.querySelector<HTMLElement>("#wallet-status")!;

button.addEventListener("click", async () => {
button.disabled = true;
status.textContent = "지갑 승인을 기다리는 중입니다.";

try {
const connection = await connectSelectedWallet(metamask.id, sepolia.chainId);

status.textContent =
`연결됨: ${connection.account} (${connection.chainId})`;
} catch (error) {
status.textContent =
error instanceof Error ? error.message : "지갑 연결에 실패했습니다.";
} finally {
button.disabled = false;
}
});

사용자가 승인하면 다음 값이 채워집니다.

필드예시사용 목적
accounteip155:11155111:0x...주 계정의 CAIP-10 식별자
accounts["0x..."]지갑이 승인한 원시 주소 목록
chainIdeip155:11155111활성 CAIP-2 네트워크
connectorId커넥터 식별자연결 구현 구분
transport?injected, relay, connect-service, a2a연결 전송 방식

AppKit의 내장 커넥터는 transport를 항상 설정합니다. 직접 구현한 외부 커넥터에서만 값이 없을 수 있습니다. 연결 성공은 accountchainId로 판단합니다. 전체 타입은 AppKit Types에서 확인합니다.

5. 오류별 사용자 안내

오류사용자에게 제공할 행동
USER_REJECTED선택 화면으로 돌아가 재시도할 수 있게 합니다.
WALLET_NOT_FOUND해당 지갑 정보의 downloadLink를 안내합니다.
UNSUPPORTED_CHAIN지원 네트워크를 다시 선택하게 합니다.
INVALID_CONFIG연결 버튼을 비활성화하고 초기화·허용 목록 설정을 점검합니다.
BACKEND_ERROR, SESSION_TIMEOUT, INVALID_RESPONSE모바일·백엔드 연결 상태를 안내하고 새 연결을 시작하게 합니다.

오류 enum과 상세 조건은 AppKit 오류 처리AppResultCode를 기준으로 분기합니다.

텔레메트리 전송 항목을 확인하세요

텔레메트리를 켜면(telemetry: true 또는 옵션 객체) 연결 계정 주소가 전송될 수 있습니다. 기본값은 비활성이므로, 운영 정책을 검토하기 전에는 telemetry를 넘기지 않습니다.

다음 단계