본문으로 건너뛰기

Sui 거래소 통합 가이드

이 문서는 Sui 네트워크의 네이티브 토큰인 SUI를 암호화폐 거래소에 통합하는 방법을 설명한다. 통합을 구현하는 구체적인 요구 사항과 프로세스는 거래소마다 다르다. 단계별 가이드를 제공하기보다는, 이 문서는 통합을 완료하는 데 필요한 주요 작업에 대한 정보를 제공한다. 통합을 구성하는 방법에 대한 안내 뒤에는 Sui 네트워크의 staking과 관련된 정보와 code samples도 찾을 수 있다.

SUI 통합 구성 요구 사항

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

  • Sui 풀 노드. 자체 Sui 풀 노드를 운영하거나 노드 운영자가 제공하는 풀 노드를 사용할 수 있다.
  • Sui 풀 노드 실행을 위한 권장 하드웨어 요구 사항:
    • CPU: 물리 코어 8개 / 16 vCPUs
    • RAM: 128 GB
    • 스토리지 (SSD): 4 TB NVMe drive

가장 좋은 결과를 얻으려면 Linux에서 Sui 풀 노드를 실행한다. Sui는 Ubuntu와 Debian distributions를 지원한다. macOS에서도 풀 노드를 실행할 수 있다.

Sui 풀 노드 구성

Sui GitHub repository의 Docker 또는 소스 코드를 사용해 풀 노드를 설정하고 구성하는 방법은 Sui 풀 노드 구성을 참조한다.

Sui 주소 설정

Sui 주소는 on-chain 초기화가 필요하지 않으며, 개인 키에 대응하기만 하면 주소에서 자금을 사용할 수 있다. 공개 키 바이트에 서명 체계 flag 바이트를 이어 붙인 flag || pubkeyBLAKE2b (256 bits output) hashing function으로 해시하면 32-byte Sui 주소를 파생할 수 있다.

현재 Sui 주소는 pure Ed25519, Secp256k1, Secp256r1, multisig 서명 체계를 지원한다. 각 flag byte는 각각 0x00, 0x01, 0x02, 0x03이다.

다음 code sample은 Rust에서 Sui 주소를 파생하는 방법을 보여준다:

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);

주소 표시

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

주소의 잔액 변경 추적

미리 정한 간격으로 suix_getBalance를 호출해 잔액 변화를 추적할 수 있다. 이 호출은 주소의 총 잔액를 반환한다. total에는 모든 코인 또는 토큰 type이 포함되지만, 이 문서는 SUI에 초점을 맞춘다. 연속적인 suix_getBalance 요청 사이의 총 잔액 변화를 추적할 수 있다.

다음 bash 예시는 주소 suix_getBalance에 대해 0x849d63687330447431a2e76fecca4f3c10f6884ebaa9909674123c6c662612a3를 사용하는 방법을 보여준다. Devnet이 아닌 네트워크를 사용한다면 rpc 값을 해당 풀 노드 URL로 바꾼다.

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

응답은 주소의 totalBalance를 포함하는 JSON 객체이다:

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

}
},
"id":1
}

다음 예시는 Rust에서 suix_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(())
}

이벤트로 주소의 잔액 변경 추적

주소에서 발생한 모든 이벤트를 구독해 잔액을 추적할 수도 있다. 주소가 코인을 획득하거나 가스 수수료를 지불하는 경우처럼 SUI 코인과 관련된 이벤트만 포함하도록 filter를 사용한다. 다음 예시는 bash와 cURL을 사용해 주소의 이벤트를 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

응답에는 많은 이벤트가 포함될 수 있다. 요청에서 nextCursor 키를 사용해 응답에 pagination을 추가한다. 트랜잭션의 txDigest 필드에서 대응되는 eventSeqid를 확인할 수 있다.

null 배열의 첫 번째 paramstxDigest 값으로 바꾸면 특정 지점부터 pagination을 시작할 수 있다. 두 번째 null은 반환할 결과 수(최대 1000)를 정의하는 정수이고, true는 오름차순을 의미한다. 응답이 원하는 지점부터 시작하도록 nextCursor를 사용할 수 있다.

트랜잭션의 id 필드는 다음과 같은 형태이다:

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

이 데이터를 사용해 다음과 같이 nextCursor를 만든다:

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

블록 vs 체크포인트

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

  • Sui는 체크포인트를 생성하고 finalized 트랜잭션을 추가한다. 트랜잭션은 체크포인트에 포함되기 전에도 finalized된다는 점에 유의한다.
  • 체크포인트는 fork, roll back, reorganize되지 않는다.
  • Sui는 초당 약 네 개의 체크포인트를 만든다. 최신 통계는 Sui public dashboard에서 확인한다.

체크포인트 API 작업

Sui 체크포인트 API 작업에는 다음이 포함된다:

SUI 잔액 전송

주소 사이에 특정 양의 SUI를 전송하려면 정확히 그 값만큼 들어 있는 SUI 토큰 객체가 필요하다. Sui에서는 SUI 토큰을 포함해 모든 것이 객체이다. 각 SUI 토큰 객체에 들어 있는 SUI 양은 다르다. 예를 들어 어떤 주소는 각각 다른 값을 가지는 3개의 SUI 토큰을 소유할 수 있다: 하나는 0.1 SUI, 둘째는 1.0 SUI, 셋째는 0.005 SUI이다. 이 주소의 총 잔액는 각 SUI 토큰 객체 값의 합으로, 이 경우 1.105 SUI이다.

특정 값을 가진 토큰 객체를 만들기 위해 SUI 토큰 객체를 merge하거나 split할 수 있다. 예를 들어 0.6 SUI 가치의 SUI 토큰을 만들려면, 1 SUI 토큰을 0.6 SUI와 0.4 SUI의 두 토큰 객체로 split한다.

특정 양의 SUI를 전송하려면 그 양만큼의 SUI 토큰이 필요하다. 정확한 값을 가진 SUI 토큰을 얻으려면 기존 SUI 토큰을 split하거나 merge해야 할 수 있다. Sui는 이를 수행하는 여러 방법을 지원하며, 그중 일부는 코인을 수동으로 split하거나 merge할 필요가 없다.

전송용 Sui API 작업

Sui는 주소 사이의 SUI 전송과 관련하여 다음 API 작업을 지원한다:

  • sui_transferObject: SUI 토큰은 객체이므로 다른 객체처럼 전송할 수 있다. 이 method는 gas 토큰을 요구하며, 특수한 경우에만 유용하다.

  • sui_payAllSui: 이 method는 SUI 토큰 IDs 배열을 받는다. 기존 토큰을 모두 하나로 merge하고 가스 수수료를 차감한 뒤, merge된 토큰을 recipient 주소로 보낸다.

    이 method는 주소의 모든 SUI를 전송하려는 경우 특히 유용하다. 주소의 모든 코인을 하나로 합치려면 recipient를 같은 주소로 설정한다. 이는 native Sui method이며 내부 merge 작업에 대해 추가적인 on-chain 트랜잭션 객체를 생성하지 않는다.

  • sui_paySui: 이 operation은 SUI 토큰 IDs 배열, amounts 배열, recipient 주소 배열을 받는다.

    amounts와 recipients 배열은 일대일로 대응한다. recipient 주소가 하나뿐이더라도 amount 배열의 각 값마다 recipient를 포함해야 한다.

    이 operation은 제공한 모든 토큰을 하나의 토큰 객체로 merge하고 가스 수수료를 정산한다. 그런 다음 amounts 배열에 따라 토큰을 split해 첫 번째 토큰을 첫 번째 recipient로, 두 번째 토큰을 두 번째 recipient로 보내는 식으로 전송한다. 토큰에 남은 SUI는 source 주소에 남는다.

    이 method의 장점은 토큰 merge나 split에 대한 가스 수수료가 없고, 토큰 merge와 split이 추상화된다는 점이다. sui_paySui operation은 native function이므로 merge와 split 작업은 Sui 트랜잭션으로 간주되지 않는다. 이들에 대한 가스 수수료는 Sui의 일반 트랜잭션과 동일하다. recipient를 자기 own 주소로 설정하면 이 operation을 사용해 own 주소 안에서 코인을 split할 수 있다. 입력 코인의 총값은 전송할 amounts의 총합보다 커야 한다는 점에 유의한다.

  • sui_pay: 이 method는 sui_paySui와 유사하지만 SUI뿐 아니라 모든 종류의 코인 또는 토큰을 받을 수 있다. gas 토큰을 포함해야 하며, 모든 코인 또는 토큰은 같은 type이어야 한다.

  • sui_transferSui: 이 method는 SUI 토큰 객체 하나와 recipient에게 보낼 amount만 받는다. 같은 토큰을 가스 수수료에도 사용하므로 전송 amount는 사용된 SUI 토큰 값보다 엄격히 작아야 한다.

트랜잭션 서명

서명 유효성 요구 사항에 대한 자세한 내용은 서명 요구 사항을 참고한다.

SUI 스테이킹

Sui 블록체인은 Delegated Proof-of-Stake 메커니즘(DPoS)을 사용한다. 이를 통해 SUI 토큰 holders는 원하는 validator에 SUI 토큰을 stake할 수 있다. 누군가가 SUI 토큰을 stake한다는 것은 해당 토큰이 에포크 전체 동안 lock된다는 뜻이다. 사용자는 언제든지 stake를 인출할 수 있지만, 새로운 staking 요청은 다음 에포크 시작 시점에만 활성화된다.

validators에 토큰을 stake한 SUI holders는 Sui 네트워크 보안에 기여한 대가로 rewards를 얻는다. Sui는 네트워크의 stake rewards를 기반으로 staking rewards를 결정하고 각 에포크의 끝에서 이를 분배한다.

Sui 네트워크의 총 voting power는 항상 10,000이다. 각 validator의 voting power는 basis points와 유사하다. 예를 들어 voting power 101은 1.01%이다. Sui의 quorum threshold(트랜잭션을 확인하는 데 필요한 votes 수)는 6,667(2/3 초과)이다. 개별 validator의 voting power는 validator가 얼마나 많은 stake를 보유했는지와 관계없이 1,000(10%)으로 제한된다.

스테이킹 함수

Sui는 staking과 관련된 다음 API 작업을 지원한다. 소스 코드는 sui_system 모듈에서 찾을 수 있다.

  • request_add_stake: validator의 staking pool에 user stake를 추가한다.

    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: 여러 코인을 사용해 validator의 staking pool에 user stake를 추가한다.

    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: lock된 SUI 코인을 사용해 validator의 staking pool에 user stake를 추가한다.

    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: validator의 staking pool에서 user stake의 일부를 인출한다.

    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,
    );
    }