본문으로 건너뛰기

Sui 거래소 통합 가이드

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

Requirements to configure a SUI integration

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

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

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

Configure a Sui full node

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

Set up Sui addresses

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

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

다음 code sample은 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 calls와 사용자 addresses를 표시할 때 항상 0x prefix를 포함할 것을 권장한다.

Track balance changes for an address

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

다음 bash 예시는 address 0x849d63687330447431a2e76fecca4f3c10f6884ebaa9909674123c6c662612a3에 대해 suix_getBalance를 사용하는 방법을 보여준다. 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

응답은 address의 totalBalance를 포함하는 JSON object이다:

{
"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(())
}

Use events to track balance changes for an address

address에서 발생한 모든 events를 구독해 balance를 추적할 수도 있다. address가 coin을 획득하거나 gas fee를 지불하는 경우처럼 SUI coins와 관련된 events만 포함하도록 filter를 사용한다. 다음 예시는 bash와 cURL을 사용해 address의 events를 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

응답에는 많은 events가 포함될 수 있다. 요청에서 nextCursor key를 사용해 응답에 pagination을 추가한다. transaction의 id field에서 대응되는 txDigesteventSeq를 확인할 수 있다.

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

transaction의 id field는 다음과 같은 형태이다:

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

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

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

Blocks vs checkpoints

Sui는 DAG 기반 blockchain이며 node synchronization과 전역 transaction ordering을 위해 checkpoints를 사용한다. Checkpoints는 blocks와 다음과 같은 점에서 다르다:

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

Checkpoint API operations

Sui Checkpoint API operations에는 다음이 포함된다:

SUI balance transfer

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

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

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

Sui API operations for transfers

Sui는 addresses 사이의 SUI 전송과 관련하여 다음 API operations를 지원한다:

  • sui_transferObject: SUI tokens는 objects이므로 다른 object처럼 전송할 수 있다. 이 method는 gas token을 요구하며, 특수한 경우에만 유용하다.

  • sui_payAllSui: 이 method는 SUI token IDs 배열을 받는다. 기존 tokens를 모두 하나로 merge하고 gas fee를 차감한 뒤, merge된 token을 recipient address로 보낸다.

    이 method는 address의 모든 SUI를 전송하려는 경우 특히 유용하다. address의 모든 coins를 하나로 합치려면 recipient를 같은 address로 설정한다. 이는 native Sui method이며 내부 merge 작업에 대해 추가적인 on-chain transaction objects를 생성하지 않는다.

  • sui_paySui: 이 operation은 SUI token IDs 배열, amounts 배열, recipient addresses 배열을 받는다.

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

    이 operation은 제공한 모든 tokens를 하나의 token object로 merge하고 gas fees를 정산한다. 그런 다음 amounts 배열에 따라 token을 split해 첫 번째 token을 첫 번째 recipient로, 두 번째 token을 두 번째 recipient로 보내는 식으로 전송한다. token에 남은 SUI는 source address에 남는다.

    이 method의 장점은 token merge나 split에 대한 gas fees가 없고, token merge와 split이 추상화된다는 점이다. sui_paySui operation은 native function이므로 merge와 split 작업은 Sui transactions로 간주되지 않는다. 이들에 대한 gas fees는 Sui의 일반 transaction과 동일하다. recipient를 자기 own address로 설정하면 이 operation을 사용해 own address 안에서 coins를 split할 수 있다. 입력 coins의 총값은 전송할 amounts의 총합보다 커야 한다는 점에 유의한다.

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

  • sui_transferSui: 이 method는 SUI token object 하나와 recipient에게 보낼 amount만 받는다. 같은 token을 gas fees에도 사용하므로 전송 amount는 사용된 SUI token 값보다 엄격히 작아야 한다.

Signing transactions

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

SUI staking

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

validators에 tokens를 stake한 SUI holders는 Sui network 보안에 기여한 대가로 rewards를 얻는다. Sui는 network의 stake rewards를 기반으로 staking rewards를 결정하고 각 epoch의 끝에서 이를 분배한다.

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

Staking functions

Sui는 staking과 관련된 다음 API operations를 지원한다. source code는 sui_system module에서 찾을 수 있다.

  • 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: 여러 coins를 사용해 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 coin을 사용해 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,
    );
    }