본문으로 건너뛰기

거래소 통합 가이드

이 토픽은 Sui 네트워크에 네이티브한 token인 SUI를 암호화폐 거래소에 통합하는 방법을 설명한다. 통합을 구현하기 위한 구체적인 요구 사항과 프로세스는 거래소마다 다르다. 단계별 가이드를 제공하는 대신, 이 토픽은 통합을 완료하는 데 필요한 주요 작업에 대한 정보를 제공한다. 통합을 구성하는 방법에 대한 안내 이후에는 Sui 네트워크에서의 stake/staking과 관련된 정보 및 코드 샘플도 확인할 수 있다.

Requirements to configure a SUI integration

SUI 통합을 구성하기 위한 요구 사항은 다음을 포함한다:

  • Sui 풀 노드이다. 자체 Sui 풀 노드를 운영하거나 노드 운영자의 풀 노드를 사용할 수 있다.
  • Sui 풀 노드를 실행하기 위한 권장 최소 하드웨어:
    • CPU: 8 물리 코어 / 16 vCPU
    • RAM: 128 GB
    • 스토리지(SSD): 4 TB NVMe 드라이브

최상의 결과를 위해 Linux에서 Sui 풀 노드를 실행한다. Sui는 Ubuntu 및 Debian 배포판을 지원한다. macOS에서도 풀 노드를 실행할 수 있다.

Configure a Sui full node

Docker를 사용하거나 Sui GitHub 리포지토리의 소스 코드에서 직접 Sui 풀 노드를 설정하고 구성할 수 있다.

Install a Sui full node using Docker

이 섹션의 각 명령은 리포지토리의 동일한 branch를 사용하여 실행한다. branch-name을 사용하는 branch로 바꾼다. 예를 들어 Sui Devnet 네트워크를 사용하려면 devnet을 사용하고, Sui Testnet 네트워크를 사용하려면 testnet을 사용한다. 모든 파일을 동일한 폴더 위치로 다운로드해야 하며, 모든 명령을 동일한 폴더 위치에서 실행해야 한다.

  1. DockerDocker Compose를 설치한다. Docker Desktop 버전은 Docker Compose를 설치한다.

  2. Linux용 dependencies를 설치한다:

    $ apt update \
    && apt install -y --no-install-recommends \
    tzdata \
    ca-certificates \
    build-essential \
    pkg-config \
    cmake
  3. docker-compose.yaml 파일을 다운로드한다:

    $ wget https://github.com/MystenLabs/sui/blob/branch-name/docker/fullnode/docker-compose.yaml
  4. fullnode-template.yaml 파일을 다운로드한다:

    $ wget https://github.com/MystenLabs/sui/raw/branch-name/crates/sui-config/data/fullnode-template.yaml
  5. genesis.blob 파일을 다운로드한다:

    $ wget https://github.com/MystenLabs/sui-genesis/blob/main/mainnet/genesis.blob
    정보

    필요하다면 URL의 mainnet을 원하는 대상 branch로 바꾼다. 예를 들어 Testnet의 경우 https://github.com/MystenLabs/sui-genesis/blob/main/testnet/genesis.blob이다.

  6. 풀 노드를 시작한다. -d 스위치는 백그라운드(detached mode)에서 시작한다.

    $ docker-compose up -d

Install a Sui full node from source

이 섹션의 단계는 Sui GitHub 리포지토리에서 직접 Sui 풀 노드를 설치하고 구성한다. 이 단계는 Rust 패키지 매니저인 Cargo를 사용한다.

  1. Sui의 prerequisites를 설치한다.
  2. Sui 리포지토리를 clone한다:
    $ git clone https://github.com/MystenLabs/sui.git -b branch-name
    사용할 branch로 branch-name을 바꾼다. 모든 명령에서 동일한 branch를 사용해야 한다.
  3. /sui로 디렉터리로 변경한다:
    $ cd sui
  4. fullnode.yaml template을 복사한다:
    $ cp crates/sui-config/data/fullnode-template.yaml fullnode.yaml
  5. genesis.blob 파일을 다운로드한다:
    $ wget https://github.com/MystenLabs/sui-genesis/blob/main/mainnet/genesis.blob
    필요하다면 URL의 mainnet을 이전 명령에 사용한 동일한 branch로 변경한다.
  6. 선택 사항으로, 기본값이 아닌 다른 경로에 Sui를 설치한 경우 사용한 경로를 사용하도록 fullnode.yaml 파일을 수정한다. 적절하게 db-pathgenesis-file-location에 대해 sui-fullnode를 설치한 폴더의 경로로 업데이트한다: db-path: “/db-files/sui-fullnode-folder” genesis-file-location: “/sui-fullnode-folder/genesis.blob”
  7. Sui 풀 노드를 시작한다:
    $ cargo run --release --bin sui-node -- --config-path fullnode.yaml

Set up Sui addresses

Sui address는 온체인 초기화가 필요하지 않으며, address가 자신의 private key에 대응하면 해당 address에서 지출할 수 있다. 서명 체계 플래그 바이트와 공개 키 바이트를 연결한 값 flag || pubkeyBLAKE2b (256 bits 출력) 해시 함수로 해시하여 32바이트 Sui address를 파생할 수 있다.

현재 Sui address는 다음 서명 체계를 지원한다: pure Ed25519, Secp256k1, Secp256r1, Multisig이다. 각각에 대응하는 플래그 바이트는 0x00, 0x01, 0x02, 0x03이다.

다음 코드 샘플은 Rust에서 Sui address를 파생하는 방법을 보여준다:

let flag = 0x00; // 0x00 = ED25519, 0x01 = Secp256k1, 0x02 = Secp256r1, 0x03 = MultiSig
// Hash the [flag, public key] bytearray using Blake2b
let mut hasher = DefaultHash::default();
hasher.update([flag]);
hasher.update(pk);
let arr = hasher.finalize();
let sui_address_string = hex::encode(arr);

Displaying addresses

Sui는 0x prefix가 있는 address와 없는 address를 모두 지원한다. Sui는 API 호출과 사용자 address 표시 시 항상 0x prefix를 포함할 것을 권장한다.

Track balance changes for an address

미리 정의한 간격으로 sui_getBalance를 호출하여 balance 변화를 추적할 수 있다. 이 호출은 address의 총 balance를 반환한다. 총합에는 모든 coin 또는 token 타입이 포함되지만, 이 문서는 SUI에 초점을 맞춘다. 연속된 sui_getBalance 요청 사이에서 address의 총 balance 변화를 추적할 수 있다.

다음 bash 예시는 address 0x849d63687330447431a2e76fecca4f3c10f6884ebaa9909674123c6c662612a3에 대해 sui_getBalance를 사용하는 방법을 보여준다. Devnet 이외의 네트워크를 사용하는 경우 rpc 값은 적절한 풀 노드의 URL로 바꾼다.

rpc="https://fullnode.devnet.sui.io:443"
address="0x849d63687330447431a2e76fecca4f3c10f6884ebaa9909674123c6c662612a3"
data="{\"jsonrpc\": \"2.0\", \"method\": \"sui_getBalance\", \"id\": 1, \"params\": [\"$address\"]}"
curl -X POST -H 'Content-type: application/json' --data-raw "$data" $rpc

응답은 address에 대한 totalBalance를 포함하는 JSON object이다:

{
"jsonrpc": "2.0",
"result": {
"coinType": "0x2::sui::SUI",
"coinObjectCount": 40,
"totalBalance": 10000000000,
"lockedBalance": {}
},
"id": 1
}

다음 예시는 Rust에서 sui_getBalance를 사용하는 방법을 보여준다:

use std::str::FromStr;
use sui_sdk::types::base_types::SuiAddress;
use sui_sdk::{SuiClient, SuiClientBuilder};


#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let sui = SuiClientBuilder::default().build(
"https://fullnode.devnet.sui.io:443",
).await.unwrap();
let address = SuiAddress::from_str("0x849d63687330447431a2e76fecca4f3c10f6884ebaa9909674123c6c662612a3")?;
let objects = sui.read_api().get_balance(address).await?;
println!("{:?}", objects);
Ok(())
}

Use events to track balance changes for an address

address에서 발생한 모든 event를 구독하여 address의 balance를 추적할 수도 있다. address가 coin을 획득하거나 가스 수수료를 지불하는 경우처럼 SUI 코인과 관련된 event만 포함하도록 filter를 사용한다. 다음 예시는 bash와 cURL을 사용해 address에 대한 event를 filter하는 방법을 보여준다:

rpc="https://fullnode.devnet.sui.io:443"
address="0x849d63687330447431a2e76fecca4f3c10f6884ebaa9909674123c6c662612a3"
data="{\"jsonrpc\": \"2.0\", \"id\":1, \"method\": \"sui_getEvents\", \"params\": [{\"Recipient\": {\"AddressOwner\": \"0x849d63687330447431a2e76fecca4f3c10f6884ebaa9909674123c6c662612a3\"}}, null, null, true ]}"
curl -X POST -H 'Content-type: application/json' --data-raw "$data" $rpc

응답에는 많은 수의 event가 포함될 수 있다. 요청에서 nextCursor 키를 사용하여 응답에 pagination을 추가한다. transaction의 id 필드에서 해당하는 txDigesteventSeq를 확인할 수 있다.

params 내의 첫 번째 null 대신 txDigest 값을 추가할 수 있다. 두 번째 null은 반환할 결과 수(최대 1000개)를 정의하는 정수이며, true는 오름차순을 의미한다. nextCursor를 사용하여 응답이 원하는 지점에서 시작하도록 할 수 있다.

모든 transaction의 id 필드는 다음과 같다:

"id": {
"txDigest": "GZQN9pE3Zr9ZfLzBK1BfVCXtbjx5xKMxPSEKaHDvL3E2",
"eventSeq": 6019
}

이 데이터를 사용해 다음과 같이 nextCursor를 생성한다:

nextCursor : {"txDigest": "GZQN9pE3Zr9ZfLzBK1BfVCXtbjx5xKMxPSEKaHDvL3E2","eventSeq": 6019}

Blocks vs Checkpoints

Sui는 DAG 기반 블록체인이며 노드 동기화와 전역 transaction ordering을 위해 체크포인트를 사용한다. 체크포인트는 다음 방식에서 블록과 다르다:

  • Sui는 체크포인트를 생성하고 확정된 transaction을 추가한다. transaction은 체크포인트에 포함되기 전에도 확정된다는 점에 유의한다.
  • 체크포인트는 fork, roll back, reorg를 하지 않는다.
  • Sui는 초당 약 4개의 체크포인트를 생성한다. 가장 최신 통계는 Sui public dashboard에서 확인한다.

Checkpoint API operations

Sui 체크포인트 API operations는 다음을 포함한다:

  • sui_getCheckpoint - 지정된 체크포인트를 가져온다.
  • sui_getLatestCheckpointSequenceNumber - 가장 최근에 실행된 체크포인트의 sequence number를 가져온다.
  • sui_getCheckpoints - 지정된 interval 동안 발생한 체크포인트의 paginated 목록을 가져온다. 향후 릴리스에서 제공될 예정이다.

SUI Balance transfer

address 간에 특정 amount의 SUI를 전송하려면 해당 값의 SUI token object가 필요하다. Sui에서는 SUI token을 포함해 모든 것이 object이다. 각 SUI token object에 들어 있는 SUI amount는 서로 다르다. 예를 들어 어떤 address는 값이 서로 다른 3개의 SUI token을 소유할 수 있는데, 하나는 0.1 SUI, 두 번째는 1.0 SUI, 세 번째는 0.005 SUI이다. 이 경우 address의 총 balance는 개별 SUI token object 값의 합이며, 이 경우 1.105 SUI이다.

SUI token object를 merge하거나 split하여 특정 값의 token object를 만들 수 있다. 값이 .6 SUI인 SUI token을 만들기 위해, 값이 1 SUI인 token을 .6 SUI와 .4 SUI인 두 token object로 split한다.

특정 amount의 SUI를 전송하려면 해당 amount의 SUI token이 필요하다. 해당 값의 SUI token을 얻기 위해 기존 SUI token을 split하거나 merge해야 할 수 있다. Sui는 이를 수행하는 여러 method를 지원하며, 수동으로 코인을 split하거나 merge할 필요가 없는 method도 포함한다.

Sui API operations for transfers

Sui는 address 간 SUI 전송과 관련하여 다음 API operations를 지원한다:

  • sui_transferObject SUI token은 object이므로 다른 object와 동일하게 SUI token을 전송할 수 있다. 이 method는 가스 token이 필요하며, niche cases에서만 유용하다.

  • sui_payAllSui 이 method는 SUI token ID 배열을 받는다. 이 method는 기존의 모든 token을 하나로 merge하고, 가스 수수료를 차감한 다음, merge된 token을 수신자 address로 전송한다.

    이 method는 address에서 모든 SUI를 전송하려는 경우 특히 유용하다. address의 모든 코인을 merge하려면 수신자를 동일한 address로 설정한다. 이는 네이티브 Sui method이므로 Sui에서 transaction으로 간주되지 않는다.

  • sui_paySui 이 operation은 SUI token ID 배열, amount 배열, 그리고 수신자 address 배열을 받는다.

    amount 배열과 recipients 배열은 일대일로 매핑된다. 단 하나의 수신자 address만 사용하더라도, amount 배열의 각 amount에 대해 이를 포함해야 한다.

    이 operation은 제공된 모든 token을 하나의 token object로 merge하고 가스 수수료를 정산한다. 그런 다음 amounts 배열의 amount에 따라 token을 split하고, 첫 번째 token은 첫 번째 수신자에게, 두 번째 token은 두 번째 수신자에게, 이런 식으로 전송한다. token에 남은 SUI는 source address에 남는다.

    이 method의 이점은 다음을 포함한다: token을 merge하거나 split하는 가스 수수료가 없고, 추상화된 token merge와 split이다. sui_paySui operation은 네이티브 함수이므로 merge와 split operation은 Sui transaction으로 간주되지 않는다. 이들에 대한 가스 수수료는 일반적인 Sui transaction과 일치한다. 수신자를 자신의 address로 설정하여 자신의 address에서 코인을 split하는 데에도 이 operation을 사용할 수 있다. 전송할 amount들의 총합보다 input coin들의 총 가치가 커야 한다는 점에 유의한다.

  • sui_pay 이 method는 sui_paySui와 유사하지만, SUI만이 아니라 모든 종류의 coin 또는 token을 받는다. 가스 token을 포함해야 하며, 모든 coin 또는 token은 동일한 타입이어야 한다.

  • sui_transferSui 이 method는 단 하나의 SUI token object와 수신자에게 보낼 amount만 받는다. 이 method는 가스 수수료에 동일한 token을 사용하므로, 전송 amount는 사용되는 SUI token의 값보다 엄격히 작아야 한다.

Signing Transactions

서명 유효성 요구 사항에 대한 자세한 내용은 Sui Signatures를 참조한다.

SUI Staking

Sui 블록체인은 위임 지분 증명 (DPoS) 메커니즘을 사용한다. 이는 SUI token holder가 자신이 선택한 어떤 validator에도 SUI token을 stake/staking할 수 있게 한다. 누군가 SUI token을 stake/staking하면, 해당 token은 전체 epoch 동안 잠긴다는 의미이다. 사용자는 언제든지 자신의 stake를 인출할 수 있지만, 새로운 stake/staking 요청은 다음 epoch의 시작에서만 활성화된다.

token을 validators에 stake/staking한 SUI holder는 Sui 네트워크의 보안을 돕는 대가로 보상을 얻는다. Sui는 네트워크의 stake 보상에 기반하여 stake/staking 보상을 결정하고, 각 epoch의 끝에서 이를 분배한다.

Sui Network의 총 투표권은 항상 10,000이다. 각 개별 validator의 투표권은 basis points와 유사하다. 예를 들어, 투표권 101 = 1.01%이다. Sui의 정족수 임계값(transaction을 확인하는 데 필요한 투표 수)은 6,667(2/3 초과)이다. 단일 validator의 투표권은 validator가 보유한 stake의 양과 관계없이 1,000(10%)으로 제한된다.

Staking functions

Sui는 stake/staking과 관련된 다음 API operations를 지원한다. 소스 코드는 sui_system 모듈에서 확인할 수 있다.

  • request_add_stake 사용자 stake를 validator의 staking pool에 추가한다.
public fun request_add_stake(
self: &mut SuiSystemState,
stake: Coin<SUI>,
validator_address: address,
ctx: &mut TxContext,
) {
validator_set::request_add_stake(
&mut self.validators,
validator_address,
coin::into_balance(stake),
option::none(),
ctx,
);
}
  • request_add_stake_mul_coin 여러 코인을 사용해 사용자 stake를 validator의 staking pool에 추가한다.
public fun request_add_stake_mul_coin(
self: &mut SuiSystemState,
delegate_stakes: vector<Coin<SUI>>,
stake_amount: option::Option<u64>,
validator_address: address,
ctx: &mut TxContext,
) {
let balance = extract_coin_balance(delegate_stakes, stake_amount, ctx);
validator_set::request_add_stake(&mut self.validators, validator_address, balance, option::none(), ctx);
}
  • request_add_stake_with_locked_coin locked SUI coin을 사용해 사용자 stake를 validator의 staking pool에 추가한다.
public fun request_add_stake_with_locked_coin(
self: &mut SuiSystemState,
stake: LockedCoin<SUI>,
validator_address: address,
ctx: &mut TxContext,
) {
let (balance, lock) = locked_coin::into_balance(stake);
validator_set::request_add_stake(&mut self.validators, validator_address, balance, option::some(lock), ctx);
}
  • request_withdraw_stake 사용자 stake의 일부를 validator의 staking pool에서 인출한다.
public fun request_withdraw_stake(
self: &mut SuiSystemState,
delegation: &mut Delegation,
staked_sui: &mut StakedSui,
principal_withdraw_amount: u64,
ctx: &mut TxContext,
) {
validator_set::request_withdraw_stake(
&mut self.validators,
delegation,
staked_sui,
principal_withdraw_amount,
ctx,
);
}