신뢰 없는 에스크로를 위한 유한 상태 머신: TON에서 분산형 마켓플레이스를 구축한 방법

발행: (2026년 2월 28일 오후 01:21 GMT+9)
12 분 소요
원문: Dev.to

Source: Dev.to

텔레그램 채널을 구매하는 것은 위험합니다. 모든 소셜 미디어 자산을 구매하는 것도 위험합니다. 판매자가 돈을 받고 사라질 수 있습니다. 구매자는 자산을 받고 결제에 대해 이의를 제기할 수 있습니다. 모든 중앙화된 에스크로 서비스는 단일 실패 지점이며, 단일 신뢰 지점입니다.

우리는 이를 해결하기 위해 ITOhub 를 구축했으며, 온체인 에스크로, 오라클 확인 전송, 그리고 FunC 스마트 계약 내 Finite State Machine (FSM) 으로 완전히 관리되는 거래 라이프사이클을 제공했습니다.

아래는 FSM이 온체인 비즈니스 로직에 완벽한 패턴인 이유와 우리가 이를 구현한 방법에 대한 개요입니다.

Source:

왜 유한 상태 머신인가?

모든 마켓플레이스 거래는 예측 가능한 흐름을 따릅니다:

created → funded → asset transferred → confirmed → completed (or disputed)

그것은 교과서적인 FSM 영역입니다.

스마트 계약에 흐름을 인코딩하는 이점

  • 모든 상태 전환이 온‑체인 – 완전히 감사 가능.
  • 잘못된 전환은 불가능 – 계약이 이를 거부합니다.
  • 행복한 경로에 인간 중재자가 필요 없습니다.
  • 분쟁 해결에 명확한 규칙 – “감각”이 아닙니다.

FSM 다이어그램

┌─────────┐    fund()    ┌──────────┐   confirm_transfer()   ┌────────────────┐
│ CREATED │────────────▶│  FUNDED  │──────────────────────▶│ TRANSFER_SENT │
└─────────┘              └──────────┘                        └───────┬───────┘
     │                        │                                      │
     │ cancel()               │ cancel()              oracle_confirm()
     │                        │                                      │
     ▼                        ▼                                      ▼
┌─────────┐              ┌──────────┐                       ┌───────────────┐
│CANCELLED│              │ REFUNDED │                       │   COMPLETED   │
└─────────┘              └──────────┘                       └───────────────┘

                                                              auto‑release
                                                              vault → seller

스마트 계약 (FunC)

TON의 스마트 계약은 TVM (TON Virtual Machine) 위에서 실행됩니다. 우리는 FunC를 선택했습니다 – Tact보다 낮은 수준이지만 가스와 메시지 처리에 대한 정밀한 제어를 제공합니다.

상태 인코딩

;; Deal states
const int STATE_CREATED       = 0;
const int STATE_FUNDED        = 1;
const int STATE_TRANSFER_SENT = 2;
const int STATE_COMPLETED     = 3;
const int STATE_CANCELLED     = 4;
const int STATE_REFUNDED      = 5;
const int STATE_DISPUTED      = 6;

;; Deal storage layout
;; [state:3][buyer:267][seller:267][amount:120][asset_id:256][oracle:267][created_at:32][timeout:32]

상태 전이 가드

각 작업은 실행하기 전에 현재 상태를 확인합니다. 이것이 핵심 FSM 강제 적용입니다:

() assert_state(int current, int expected) impure inline {
  throw_unless(400, current == expected);  ;; 400 = invalid state transition
}

() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
  int op = in_msg_body~load_uint(32);

  ;; Load deal state
  var ds = get_data().begin_parse();
  int state = ds~load_uint(3);
  slice buyer  = ds~load_msg_addr();
  slice seller = ds~load_msg_addr();
  int amount   = ds~load_coins();
  ;; ... rest of fields

  if (op == op::fund) {
    assert_state(state, STATE_CREATED);
    ;; Verify sender is buyer
    throw_unless(401, equal_slices(sender, buyer));
    ;; Verify sent amount matches deal
    throw_unless(402, msg_value >= amount);

    ;; Transition: CREATED → FUNDED
    save_state(STATE_FUNDED, buyer, seller, amount, ...);
    return ();
  }

  if (op == op::confirm_transfer) {
    assert_state(state, STATE_FUNDED);
    ;; Only seller can confirm they've initiated the transfer
    throw_unless(401, equal_slices(sender, seller));

    ;; Transition: FUNDED → TRANSFER_SENT
    save_state(STATE_TRANSFER_SENT, buyer, seller, amount, ...);
    return ();
  }

  if (op == op::oracle_confirm) {
    assert_state(state, STATE_TRANSFER_SENT);
    ;; Only designated oracle can confirm
    throw_unless(401, equal_slices(sender, oracle));

    ;; Transition: TRANSFER_SENT → COMPLETED
    ;; Release vault to seller
    send_raw_message(build_transfer_msg(seller, amount), 64);
    save_state(STATE_COMPLETED, buyer, seller, amount, ...);
    return ();
  }

  ;; ... cancel, dispute, timeout handlers
}

금고 패턴

자금은 공유 풀에 머무르지 않고 — 각 거래마다 자체 계약 인스턴스(팩토리에서 배포된 자식 계약)를 갖습니다. 이는 다음을 의미합니다:

;; Factory contract deploys a new deal contract per transaction
() create_deal(slice buyer, slice seller, int amount, int asset_id, slice oracle) impure {
  cell state_init = build_deal_state_init(buyer, seller, amount, asset_id, oracle);
  slice deal_address = calculate_address(state_init);

  ;; Deploy deal contract with initial state
  send_raw_message(
    begin_cell()
      .store_uint(0x18, 6)          ;; external inbound message header
      .store_slice(deal_address)   ;; destination address
      .store_coins(50000000)        ;; 0.05 TON for gas
      .store_uint(6, 107)          ;; state_init flag
      .store_ref(state_init)        ;; contract code+data
      .store_uint(op::init, 32)    ;; init op
    .end_cell(),
    1
  );
}

왜 개별 계약인가?

  • Isolation – 하나의 거래에서 발생한 버그가 다른 거래의 자금을 고갈시킬 수 없습니다.
  • Deterministic vault – 각 계약은 단일 목적이며 예측 가능합니다.

Source:

오라클 레이어

가장 까다로운 부분: 텔레그램 채널이 실제로 이전되었는지 어떻게 검증하나요?

우리는 다음과 같은 확인 오라클을 구축했습니다:

  • 자산을 모니터링 (예: Bot API를 통해 텔레그램 채널 관리자 목록 확인)
  • 이전이 확인되면 온체인 확인을 전송
  • 타임아웃을 가짐 – 오라클이 N 시간 이내에 확인하지 않으면 구매자가 분쟁을 제기할 수 있음
// Oracle service (NestJS)
@Injectable()
export class TelegramTransferOracle {
  constructor(
    private readonly tonClient: TonClient,
    private readonly httpService: HttpService,
  ) {}

  // Called by off‑chain worker when transfer is detected
  async confirmTransfer(dealAddress: string, proof: TransferProof): Promise<void> {
    // Build on‑chain message
    const msg = beginCell()
      .storeUint(op::oracle_confirm, 32)
      .storeUint(proof.timestamp, 32)
      .storeSlice(proof.signature)
      .endCell();

    await this.tonClient.sendExternalMessage({
      to: dealAddress,
      body: msg,
      value: 0, // no TON needed, just a signal
    });
  }

  // Periodic health‑check – if timeout passes, expose dispute endpoint
}

TL;DR

  • FSM은 수학적으로 증명 가능한 흐름을 제공합니다 – 모든 상태 변경이 온‑체인에서 강제됩니다.
  • FunC는 세밀한 가스 제어를 통해 해당 흐름을 구현할 수 있게 해줍니다.
  • 거래당 금고 계약은 자금을 격리시켜, 단일 실패 지점을 없앱니다.
  • 오프‑체인 오라클이 현실 자산 이전을 블록체인으로 연결하여, 신뢰 없는 에스크로 사이클을 완성합니다.

이것이 ITOhub가 디지털‑자산 마켓플레이스에서 신뢰 문제를 제거하는 방식입니다.

TransferOracle

class TransferOracle {
  async verifyChannelTransfer(deal: Deal): Promise<boolean> {
    const channel = await this.telegramBot.getChat(deal.assetId);
    const admins = await this.telegramBot.getChatAdministrators(deal.assetId);

    // Check if buyer is now an admin with full rights
    const buyerAdmin = admins.find(
      a => a.user.id === deal.buyerTelegramId && a.status === 'creator',
    );

    if (buyerAdmin) {
      // Send on‑chain confirmation
      await this.tonService.sendOracleConfirmation(deal.contractAddress);
      return true;
    }

    return false;
  }

  @Cron('*/60 * * * * *') // Check every minute
  async pollPendingTransfers() {
    const pending = await this.dealRepo.findByState('TRANSFER_SENT');
    for (const deal of pending) {
      await this.verifyChannelTransfer(deal);
    }
  }
}

Oracle Trust Minimization

오라클은 두 가지 일만 할 수 있습니다: confirm(확인) 또는 not confirm(확인 안 함). 오라클은 다음을 할 수 없습니다:

  • 자금 재배치
  • 거래 조건 변경
  • 일방적으로 자금이 투입된 거래 취소

오라클이 손상될 경우 최악의 상황은 거래가 정체되는 것이며, 이는 타임아웃 메커니즘에 의해 자동으로 해결됩니다.

텔레그램 미니 앱

프론트엔드는 텔레그램 미니 앱(TMA)으로 실행되어 사용자에게 네이티브한 느낌을 제공합니다:

// Deal creation flow
async function createDeal(params: DealParams) {
  // 1. Create deal record in backend
  const deal = await api.post('/deals', {
    assetType: 'telegram-channel',
    assetId: params.channelId,
    price: params.price,
    sellerTelegramId: params.sellerId,
  });

  // 2. Deploy on‑chain contract via TonConnect
  const tx = {
    validUntil: Math.floor(Date.now() / 1000) + 600,
    messages: [
      {
        address: FACTORY_CONTRACT,
        amount: toNano('0.1').toString(), // Deploy gas
        payload: buildCreateDealPayload(deal),
      },
    ],
  };

  await tonConnect.sendTransaction(tx);

  // 3. Redirect to deal tracking page
  navigate(`/deals/${deal.id}`);
}

Production에서 얻은 교훈

  1. FSM은 감사를 간단하게 만든다 – 모든 거래는 온‑체인에 완전한 상태 이력을 가지고 있다.
  2. 거래당 계약은 배포 비용을 감당할 가치가 있다 – TON에서는 계약을 배포하는 데 약 0.05 TON(~$0.10)이 든다.
  3. 오라클 설계가 진정한 보안 과제이다 – 우리는 다중 오라클 모델(3중 중 2개 확인)로 이동하고 있다.
  4. 타임아웃은 교착 상태를 방지한다 – 각 상태에는 상대방이 취소하거나 이의를 제기할 수 있도록 하는 타임아웃이 있다.

숫자

ITOhub 출시 이후:

  • 거래 라이프사이클: CREATED → COMPLETED 평균 2.3 시간
  • 계약 버그로 인한 자금 손실 제로
  • 분쟁 비율: < 5 % (대부분 오라클 타임아웃으로 해결)

전체 거래 감사는 TON Explorer에서 확인할 수 있습니다.

이 패턴을 사용할 때

FSM + 온‑체인 볼트 패턴은 다음과 같은 마켓플레이스에 적용됩니다:

  • 자산을 프로그래밍 방식으로 검증할 수 있는 경우 (채널 소유권, 도메인 이전, NFT 보유)
  • 양쪽 모두 보호가 필요한 경우 (구매자만이 아니라)
  • 정상 흐름이 일반적인 경우 (분쟁은 예외)

다음과 같은 경우에는 잘 맞지 않습니다:

  • 자산 이전 검증에 인간의 판단이 필요한 경우
  • 자산이 물리적이며 (배송 오라클이 필요한 경우)
  • 거래량이 초단위 확정성을 요구하는 경우 (계약 배포가 지연을 초래)

오픈‑소스 계획

우리는 핵심 FSM 에스크로 계약을 재사용 가능한 템플릿으로 오픈‑소스할 계획입니다. TON에서 마켓플레이스를 구축하고 조기 접근을 원한다면 docs.itohub.org 문서를 확인하거나 Telegram 봇으로 연락 주세요.

Gerus Lab은 탈중앙화 프로토콜, AI 기반 제품, 그리고 텔레그램 네이티브 애플리케이션을 구축합니다. NFT 스테이킹부터 DCA 봇, 에스크로 시스템까지 — 복잡한 아키텍처를 실제 소프트웨어로 전환합니다.

0 조회
Back to Blog

관련 글

더 보기 »

일이 정신 건강 위험이 될 때

markdown !Ravi Mishrahttps://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fu...