Skipper Application Architecture

July 7, 2025 ยท View on GitHub

This document describes the architecture of the Skipper smart contract system. It is useful for:

  • Contributors who want to improve the protocol
  • Developers building apps on top of Skipper

๐Ÿงฑ Entities

Participant

Any TON address (e.g. a wallet) that owns governance jettons.

Governance Token

A specific Jetton used for governance. Must implement the following standards:

Jetton Lock

A smart contract that holds locked governance jettons. Required to:

  • Prevent double voting
  • Track the amount locked per participant

See "Vote for existing proposal" and "Lock tokens" for rationale.

DAO (Skipper)

The root contract that:

  • Receives messages from participants
  • Deploys proposals
  • Acts as the treasury and central governance entrypoint

Proposal

A smart contract that contains:

  • A specific action (e.g. transfer TON, call contract)
  • Voting results Each proposal is deployed as a standalone contract.

Voter

A per-participant, per-proposal contract that:

  • Stores how many tokens the participant has used to vote
  • Allows proper vote updates when lock amount changes

๐Ÿ”„ Workflow

Lock Tokens

To vote, participants must lock jettons in a Jetton Lock contract. This prevents double voting.

sequenceDiagram
  actor wallet as DAO participant
  participant jetton as Governance token

  create participant lock as Jetton lock
  wallet ->> lock: Deploy
  wallet ->> jetton: JettonTransfer
  jetton ->> lock: JettonTransferNotification
  Note over lock: Save transferred amount

Why Proxy?

Other contracts can't directly query a lock's state. Instead, the lock sends the amount and metadata to the DAO.

flowchart TD
  participant[DAO participant]
  lock[Jetton Lock]
  dao[DAO]

  participant --โœ…<br/>Message to DAO--> lock --โ„น๏ธ<br/>Inform DAO how many tokens locked--> dao
  participant --โŒ<br/>DAO doesn't know how many tokens is locked--> dao

Create a Proposal

All proposal-related messages go through the lock (proxy pattern).

sequenceDiagram
  actor wallet as DAO participant
  participant lock as Jetton lock
  participant dao as DAO

  wallet ->> lock: SendProxyMessage (RequestNewProposal)
  lock ->> dao: ProxyMessage (RequestNewProposal)
  dao ->> proposal: InitProposal
  proposal ->> voter: InitVoter

Vote for Existing Proposal

Voting flow differs from proposal creation:

  • Order: Lock -> DAO -> Voter -> Proposal
  • Rationale: only Voter knows how much has been used previously. It computes the difference.
sequenceDiagram
  actor wallet as DAO participant
  participant lock as Jetton lock
  participant dao as DAO
  participant voter as Voter
  participant proposal as Proposal

  wallet ->> lock: SendProxyMessage (VoteForProposal)
  lock ->> dao: ProxyMessage (VoteForProposal)
  dao ->> voter: UpdateVoterBalance
  voter ->> proposal: UpdateVotes

๐Ÿšจ Exit Codes

Skipper uses:

CodeDescription
132Invalid owner (Tact standard)
6901Not enough TON in message
6902Unlock date hasn't arrived
6903Not enough YES votes to execute proposal
6904Too many NO votes for proposal
6905Contract is not initialized
6906Contract already initialized
6907Proposal expired
6908Proposal already executed
6909Proxy opcode not found
6910Unlock date is insufficient to cover proposal expiration date
6911Lock period must be greater than zero
6912Lock period is too short and does not extend current unlock date
6913Amount provided must be greater than zero
6914Insufficient storage fee for the contract
6915Invalid expiration time for proposal or voter
6916Invalid owner address for jetton wallet