본문으로 건너뛰기

Wallet Standard

Sui용으로 구축된 브라우저 확장 지갑은 Wallet Standard를 사용한다. 이것은 dApp이 지갑을 자동으로 발견하고 상호작용할 수 있는 방법을 정의하는 크로스체인 표준이다.

지갑을 구축하는 경우, 헬퍼 라이브러리 @mysten/wallet-standard 는 시작하는 데 도움이 되는 타입과 유틸리티를 제공한다.

지갑 작업

Wallet Standard는 지갑 구축을 돕는 기능을 포함한다.

지갑 interface 생성

지갑을 나타내는 클래스를 생성한다. Wallet@mysten/wallet-standard interface를 사용하여 클래스가 표준을 준수하도록 한다.

import { SUI_DEVNET_CHAIN, Wallet } from '@mysten/wallet-standard';

class YourWallet implements Wallet {
get version() {
// Return the version of the Wallet Standard this implements (in this case, 1.0.0).
return '1.0.0';
}

get name() {
return 'Wallet Name';
}

get icon() {
return 'some-icon-data-url';
}

// Return the Sui chains that your wallet supports.
get chains() {
return [SUI_DEVNET_CHAIN];
}
}

feature 구현

기능은 소비자가 지갑과 상호작용하는 데 사용할 수 있는 표준 메서드이다. 지갑은 다음 기능을 구현해야 한다:

Core features

  • standard:connect - 지갑에 계정 authorization을 요청하는 데 사용한다.
  • standard:events - 계정이 추가되거나 제거되는 것과 같이 지갑 내에서 발생하는 변경 사항을 수신하는 데 사용한다.

Transaction features

Wallet Standard 위에 구축된 라이브러리(예: dApp Kit)는 트랜잭션 서명 기능이 필요하다. 최대 호환성을 위해 현재 및 레거시 메서드를 모두 구현한다:

Current methods:

  • sui:signTransaction - 사용자에게 트랜잭션에 서명하도록 요청하고 직렬화된 트랜잭션과 서명을 dApp에 반환하는 데 사용한다. 이 메서드는 트랜잭션을 실행을 위해 제출하지 않는다.
  • sui:signAndExecuteTransaction - 사용자에게 트랜잭션에 서명하도록 요청한 다음 blockchain에 실행을 위해 제출하는 데 사용한다.

Legacy methods:

  • sui:signTransactionBlock - sui:signTransaction의 레거시 버전이다. 많은 기존 dApp이 여전히 이 메서드에 의존한다.
  • sui:signAndExecuteTransactionBlock - sui:signAndExecuteTransaction의 레거시 버전이다. 많은 기존 dApp이 여전히 이 메서드에 의존한다.

Personal message signing

  • sui:signPersonalMessage - 사용자에게 개인 메시지에 서명하도록 요청하고 메시지 서명을 dApp에 반환하는 데 사용한다. 이것은 많은 dApp이 사용하는 사용자 검증 흐름에 필수적이다.

지갑 클래스의 features 속성 아래에 이러한 기능을 구현한다:

import {
StandardConnectFeature,
StandardConnectMethod,
StandardEventsFeature,
StandardEventsOnMethod,
SuiFeatures,
SuiSignPersonalMessageMethod,
SuiSignTransactionMethod,
SuiSignAndExecuteTransactionMethod,
StandardConnect,
StandardEvents,
SuiSignPersonalMessage,
SuiSignTransaction,
SuiSignAndExecuteTransaction
} from "@mysten/wallet-standard";

class YourWallet implements Wallet {
/* ... existing code from above ... */

get features(): ConnectFeature & EventsFeature & SuiFeatures {
return {
[StandardConnect]: {
version: "1.0.0",
connect: this.#connect,
},
[StandardEvents]: {
version: "1.0.0",
on: this.#on,
},
[SuiSignPersonalMessage]: {
version: "1.1.0",
signPersonalMessage: this.#signPersonalMessage,
},
[SuiSignTransaction]: {
version: "2.0.0",
signTransaction: this.#signTransaction,
},
[SuiSignAndExecuteTransaction]: {
version: "2.0.0",
signAndExecuteTransaction: this.#signAndExecuteTransaction,
},
};
};

#on: EventsOnMethod = () => {
// Your wallet's events on implementation
};

#connect: ConnectMethod = () => {
// Your wallet's connect implementation
};

#signPersonalMessage: SuiSignPersonalMessageMethod = () => {
// Your wallet's signPersonalMessage implementation
};

#signTransaction: SuiSignTransactionMethod = () => {
// Your wallet's signTransaction implementation
};

#signAndExecuteTransaction: SuiSignAndExecuteTransactionMethod = () => {
// Your wallet's signAndExecuteTransaction implementation
};
}

계정 노출

지갑 interface의 마지막 요구 사항은 accounts interface를 노출하는 것이다. Wallet Standard를 준수하는 지갑은 지갑이 로드될 때 이전에 승인된 계정으로 이 배열을 자동으로 채워야 한다. 이것은 다음을 의미한다:

  • 첫 방문 시: 사용자가 standard:connect 를 통해 계정을 승인할 때까지 배열이 비어 있다.
  • 이후 방문 시: 이전에 승인된 계정이 dApp 작업 없이 자동으로 나타난다.
  • 취소 후: 취소된 계정은 배열에 나타나지 않는다.

계정은 필요한 interface와 일치하는 계정을 구성하기 위해 ReadonlyWalletAccount 클래스를 사용한다.

import { ReadonlyWalletAccount } from '@mysten/wallet-standard';

class YourWallet implements Wallet {
get accounts() {
// Assuming we already have some internal representation of accounts:
return someWalletAccounts.map(
(walletAccount) =>
new ReadonlyWalletAccount({
address: walletAccount.suiAddress,
publicKey: walletAccount.pubkey,
// The Sui chains that this account supports. This can be a subset of the wallet's supported chains.
// These chains must exist on the wallet as well.
chains: [SUI_DEVNET_CHAIN],
// The features that this account supports. This can be a subset of the wallet's supported features.
// These features must exist on the wallet as well.
features: [
SuiSignPersonalMessage,
SuiSignTransaction,
SuiSignAndExecuteTransaction,
],
}),
);
}
}

window에 register

지갑에 대한 호환 interface가 있으면 registerWallet 함수를 사용하여 등록한다.

import { registerWallet } from '@mysten/wallet-standard';

registerWallet(new YourWallet());

지갑 관리

Wallet Standard는 앱이 지갑과 상호작용하는 데 도움이 되는 기능을 포함한다.

지갑 data

사용자 브라우저에 설치된 지갑을 쿼리하려면 getgetWallets 함수를 사용한다.

import { getWallets } from '@mysten/wallet-standard';

const availableWallets = getWallets().get();

이 호출의 반환값(이전 코드의 availableWallets)은 Wallet 타입의 배열이다.

웹 페이지에 지갑 세부 정보를 표시하려면 Wallet.iconWallet.name 속성을 사용한다.

Wallet.accountsWalletAccount의 배열이다. 각 WalletAccount 타입은 addresspublicKey 속성을 가지며, 이것은 개발 중에 가장 유용하다. 이 데이터는 계정 authorization 후 채워지고 캐시된다.

기능

Wallet 타입과 WalletAccount 타입 모두 features라는 속성을 가진다. 주요 지갑 기능은 여기에서 찾을 수 있다. 지갑이 구현해야 하는 필수 기능은 이전 코드에 나열되어 있다.

많은 지갑이 일부 비필수 기능을 생략하거나 일부 사용자 정의 기능을 추가하므로, 특정 지갑을 통합하려는 경우 관련 지갑 문서를 확인한다.

지갑 계정 authorize

Wallet Standard는 지속적인 authorization 모델을 사용한다. API의 "connect" 용어에도 불구하고, 지갑은 dApp의 작업 없이 로드될 때 이전에 승인된 계정을 자동으로 복원해야 한다. connect() 메서드는 필요할 때 사용자에게 계정을 승인하도록 요청하는 데 특화되어 있다.

How 계정 authorization works:

  1. 지갑은 계정을 자동으로 복원한다: 지갑이 로드될 때 현재 dApp에 대해 이전에 승인된 계정으로accounts 배열을 자동으로 채워야 한다. 이것은 dApp 상호작용 없이 발생한다.

  2. 필요할 때만 요청한다: 다음 경우에만connect() 를 호출한다:

    • accounts 배열이 비어 있는 경우 (첫 사용자이거나 모든 계정이 취소된 경우)
    • 현재 계정이 요구 사항을 충족하지 않는 경우 (예: 특정 chain의 계정이 필요한 경우)
    • 사용자가 명시적으로 더 많은 계정을 승인하려는 경우
await wallet.features['standard:connect'].connect();
정보

connect() 메서드는 사용자 상호작용 없이 이전에 승인된 계정을 검색하기 위한 silent 매개변수를 가지지만, 이 매개변수는 Wallet Standard의 향후 버전에서 사용 중단될 예정이다. 지갑은 대신 로드 시 accounts 배열을 자동으로 채워야 한다.

connect()를 호출하면 지갑이 사용자가 dApp이 액세스할 수 있는 계정을 선택하고 승인할 수 있도록 팝업을 연다.

계정 authorization revoke

connect(계정 authorization) 기능과 유사하게, Wallet Standard는 dApp의 지갑 계정 액세스를 취소하기 위한 standard:disconnect 도 포함한다. 다음 예제는 이 기능을 호출한다:

wallet.features['standard:disconnect'].disconnect();

트랜잭션 - 권장 approach

사용자가 지갑에서 계정을 승인하면 앱은 주소 및 메서드와 같은 트랜잭션을 실행하는 데 필요한 정보를 갖는다.

@mysten/sui 라이브러리로 트랜잭션을 별도로 구성한 다음 사용자의 개인 키로 서명한다.sui:signTransaction 기능을 사용하여 이를 달성한다:

wallet.features['sui:signTransaction'].signTransaction({
transaction: <Transaction>,
account: <WalletAccount>
})

Account authorization과 유사하게, 이 프로세스는 사용자가 트랜잭션을 수락하거나 거부할 수 있는 팝업 대화 상자를 연다. 수락하면 함수는 {bytes: String, signature: Uint8Array}형식의 객체를 반환한다. bytes 값은 트랜잭션의 b64 인코딩이고 signature 값은 트랜잭션 서명이다.

Transaction을 실행하려면 SuiGrpcClient@mysten/sui/grpc를 사용한다:

import { fromBase64 } from '@mysten/sui/utils';
import { SuiGrpcClient } from '@mysten/sui/grpc';

const client = new SuiGrpcClient({
network: 'mainnet',
baseUrl: 'https://fullnode.mainnet.sui.io:443',
});
client.core.executeTransaction({
transaction: fromBase64(bytes),
signatures: [signature],
});

트랜잭션 - 축약 approach

많은 지갑이 위의 흐름을 하나의 기능인sui:signAndExecuteTransaction으로 추상화한다. 이 기능에 필요한 인수는 원시 트랜잭션과 응답에 포함할 원하는 정보가 있는 옵션이다:

  • showEffects: Transaction effects를 포함한다.
  • showEvents: Transaction events를 포함한다.
  • showObjectChanges: 삭제, 생성 또는 변경된 모든 객체를 포함한다.
  • showBalanceChanges: 발생한 모든 코인 전송을 포함한다.
  • showInput: Transaction의 입력을 포함한다.
  • showRawInput: showInput 과 동일하지만 형식이 원시이다.

지갑이 emit하는 event

지갑은 앱이 수신할 수 있는 특정 사용자 작업에서 이벤트를 발생시킨다. 이러한 이벤트를 통해 앱이 지갑의 사용자 작업에 반응할 수 있다.

Wallet Standard는 chain, 기능 또는 계정에 적용할 수 있는 변경 이벤트만 정의한다.

  • chains: Chain의 변경 이벤트는 사용자가 Devnet에서 Testnet으로와 같이 지갑의 활성 네트워크를 전환했음을 의미한다.
  • features: 사용자가 앱이 특정 지갑 기능에 액세스할 수 있는 권한을 추가하거나 제거했다.
  • accounts: 사용자가 앱과 상호작용하기 위해 계정(주소)를 추가하거나 제거했다.

다음 호출로 앱을 이벤트에 구독한다:

const unsubscribe = wallet.features['standard:events'].on('change', callback);

이 호출은 이벤트 수신을 구독 취소하는 데 호출할 수 있는 함수를 반환한다.

콜백은 이벤트가 발생할 때 수행할 로직을 포함하는 핸들러이다. 콜백 함수에 대한 입력은 다음 타입의 객체이다:

{
accounts: WalletAccount[],
chains: IdentifierArray,
features: IdentifierRecord<unknown>
}

이러한 값은 모두 새 항목이나 변경된 항목을 포함하는 배열이다. 결과적으로, 대부분의 경우 모든 이벤트는 하나의 배열만 채우고 나머지는 비어 있다.

구현 예시

Mysten Labs는 @mysten/dapp-kit-react라는 React 기반 애플리케이션을 위한 기본 스캐폴드를 제공한다. 자세한 내용은 dApp Kit documentation 을 참고한다.