HTLC Dutch Auction Escrow API

Technical reference for the HTLC Dutch Auction escrow smart contract covering payloads, phases, validation rules, and error codes.

Overview

HTLC Dutch is a hybrid time-locked escrow with Dutch auction mechanics. It combines:

  • HTLC (Hash Time-Locked Contract): Time-based locks with a designated resolver.

  • Dutch Auction: Price decreases linearly over time after the resolver timeout.


Lock Operation

When to Use

Create a new escrow with tokens locked for a potential swap via designated resolver or Dutch auction.

Entry Point

Send jettons to the Minter contract with MinterLockPayload as the forward payload.

Tolk Structure

struct (0x3e246684) MinterLockPayload {
    quoteId: uint256,
    refundToVault: bool,
    refFee: address,
    refFeeTier: uint16,
    excesses: address,
    userSafeDeposit: coins,
    unlockData: cell,
    unlockCondition: cell,
    extraFields: Cell<MinterLockPayloadExtraFields>
}

struct MinterLockPayloadExtraFields {
    ownerPubkey: Cell<uint256>?,
    userUnlockForwardParams: Cell<ForwardParams>?,
    lockForwardParams: Cell<LockForwardParams>?
}

struct ForwardParams {
    value: coins,
    successPayload: cell?,
    rejectPayload: cell?
}

struct LockForwardParams {
    successDest: address,
    forwardParams: ForwardParams
}

struct HtlcDutchLockParams {
    initOfferAmount: coins,
    askJettonWallet: address,
    askJettonMinter: address,
    resolverData: Cell<ResolverData>,
    dutchData: Cell<DutchData>
}

struct ResolverData {
    resolver: address,
    resolverTimeout: uint64,     // seconds, converted to absolute timestamp
    resolverAskAmount: coins
}

struct DutchData {
    dutchRemaining: coins,
    dutchMinAskAmount: coins,
    dutchTimeout: uint64,        // seconds, added to resolverTimeout for absolute timestamp
    dutchResolverPublicKey: uint256?
}

TLB Schema

minter_lock_payload#3E246684
  quoteId:uint256
  refundToVault:Bool
  refFee:MsgAddress
  refFeeTier:uint16
  excesses:MsgAddress
  userSafeDeposit:Grams
  unlockData:^Cell
  unlockCondition:^Cell
  extraFields:^MinterLockPayloadExtraFields
  = MinterLockPayload;

minter_lock_payload_extra_fields#_
  ownerPubkey:(Maybe ^uint256)
  userUnlockForwardParams:(Maybe ^ForwardParams)
  lockForwardParams:(Maybe ^LockForwardParams)
  = MinterLockPayloadExtraFields;

forward_params#_
  value:Grams
  successPayload:(Maybe ^Cell)
  rejectPayload:(Maybe ^Cell)
  = ForwardParams;

lock_forward_params#_
  successDest:MsgAddress
  forwardParams:ForwardParams
  = LockForwardParams;

htlc_dutch_lock_params#_
  initOfferAmount:Grams
  askJettonWallet:MsgAddress
  askJettonMinter:MsgAddress
  resolverData:^ResolverData
  dutchData:^DutchData
  = HtlcDutchLockParams;

resolver_data#_
  resolver:MsgAddress
  resolverTimeout:uint64
  resolverAskAmount:Grams
  = ResolverData;

dutch_data#_
  dutchRemaining:Grams
  dutchMinAskAmount:Grams
  dutchTimeout:uint64
  dutchResolverPublicKey:(Maybe uint256)
  = DutchData;

Field Descriptions

MinterLockPayload

  • quoteId: Unique identifier for this escrow order.

  • refundToVault: Whether to refund tokens to the vault (true) or directly to the user (false) on failure.

  • refFee: Address receiving the referral fee.

  • refFeeTier: Referral fee tier (basis points).

  • excesses: Address to receive excess TON from gas refunds.

  • userSafeDeposit: Minimum TON deposit from the user for gas coverage.

  • unlockData: Serialized HtlcDutchLockParams.

  • unlockCondition: Contract code that defines unlock logic. For HTLC Dutch, use hex: b5ee9c72410208010002c90003f6228210066ac100bae3022282107fd9a46ebae3026c21d0fa00fa40fa40d4d74c01d001d0f82302fa40d33f31fa003002fa0031fa00d33f31d3000193d70bff92306de202c8ce24cf0b3f5003fa02c9c827fa025003fa0213cb3f226e946c12cf8195cf8312cbffe2c9c85005fa0213cececcccc9817a106f00126d01030701fe326f15d72c23d5830c0cf2bfd33f31fa4031fa4031fa003001d0fa0031fa40fa40d4d74c01d001d001fa40d33ffa00308200cb2122c300f2f48200cb2221c300f2f403fa0031fa00d33fd3000193d70bff92306de28200cb235336b9f2f48200cb2428fa4430c000f2f48200cb2525fa4430c000f2f4f8235304a006c8ce16020076cb3f5006fa02c95043a05003a0c827fa025003fa0212cb3f226e946c12cf8195cf8312cbffe2c9c85005fa0213cececcccc9817a106f00126d6f0402d66c12d0fa00fa40fa40d4d74c01d001d0056f15d72c234b7d16c4f2bfd34131fa40fa40fa00fa40d74cd08200cb265148c70514f2f48200cb2728c300f2f48200cb2824fa4430c000f2f4f82305fa40d33ffa00300bfa00fa00d33fd3000193d70bff92306de253a4bbe30f0405009e3337388200cb295373c705f2f42b8200cb2a06ba15f2f401c8cecb3f5009fa02c9c8cf84205005fa0212cb3f216e9331cf8194cf83cbffe2c9c826fa0215ce13cecc12ccc959817a10046f036d6f0401fe29fa443109fa00fa00d30001948308d71992306de2236e92303a8e178200cb2b216eb397541cc4f910c30093313b70e21bf2f4e28200cb2c53c3b9f2f4532ba15610a851c6a124a81ca05325a1a904547e80a9845305bc8e10305454015610a98419a1520abc39083094102a395be25126a18200cb2d21c2fff2f48200cb2e06008451a7bb1af2f403c8ce12cb3f500bfa02c9c85007fa0201fa0219cb3f226e946c12cf8195cf8312cbffe2c9c85007fa0215ce13cecc13ccc902817a10046f036d6f0400046f047ee39484

  • extraFields: Additional fields.

MinterLockPayloadExtraFields

  • ownerPubkey: Optional public key for external cancel authorization.

  • userUnlockForwardParams: Optional forward parameters when the user receives a refund.

  • lockForwardParams: Optional forward parameters for lock success notification.

ForwardParams

  • value: TON amount to attach to the forward message.

  • successPayload: Optional payload sent on successful operation.

  • rejectPayload: Optional payload sent on operation rejection.

LockForwardParams

  • successDest: Address to receive lock success notification.

  • forwardParams: Forward parameters for the notification message.

HtlcDutchLockParams

  • initOfferAmount: Total amount of offer tokens being locked.

  • askJettonWallet: Jetton wallet address that must receive ask jettons during unlock.

  • askJettonMinter: Minter address of the ask jetton (for verification).

  • resolverData: Serialized resolver configuration.

  • dutchData: Serialized Dutch auction configuration.

ResolverData

  • resolver: Address authorized to fill the order at a fixed price.

  • resolverTimeout: Duration (seconds) of exclusive resolver period, stored as absolute timestamp after lock.

  • resolverAskAmount: Fixed price the resolver must pay (in ask jetton).

DutchData

  • dutchRemaining: Amount of offer tokens still available (initially equals initOfferAmount).

  • dutchMinAskAmount: Minimum price at auction end (floor price).

  • dutchTimeout: Duration (seconds) for the Dutch auction after the resolver period, stored as absolute timestamp.

  • dutchResolverPublicKey: Optional public key for signature verification during the Dutch phase.

Validation Rules

  • resolverTimeout must be > 0.

  • resolverAskAmount must be > 0.

  • dutchMinAskAmount must be < resolverAskAmount.

  • askJettonWallet must be in basechain (workchain 0).

  • resolver must be in basechain (workchain 0).

State After Lock

Contract stores HtlcDutchLockParams with absolute timestamps calculated as:

  • resolverTimeout = now + input.resolverTimeout

  • dutchTimeout = now + input.resolverTimeout + input.dutchTimeout


Unlock Operation

When to Use

Execute the swap by sending ask jettons to receive offer tokens. Behavior depends on the current time.

Entry Point

Send jettons to the Minter contract with MinterUnlockPayload as the forward payload.

Tolk Structure

struct (0x6a58f85c) MinterUnlockPayload {
    quoteId: uint256,
    resolverFillToVault: bool,
    resolverRefundToVault: bool,
    excesses: address,
    unlockArgs: cell,
    forwardParams: Cell<ForwardParams>?
}

struct HtlcDutchTryUnlockArgs {
    minOut: coins,                       // minimum acceptable offer tokens
    dutchIgnoreRefundAmount: coins,      // skip refund if overpayment < this
    dutchSignature: bits512?             // signature from dutchResolverPublicKey
}

TLB Schema

minter_unlock_payload#6A58F85C
  quoteId:uint256
  resolverFillToVault:Bool
  resolverRefundToVault:Bool
  excesses:MsgAddress
  unlockArgs:^Cell
  forwardParams:(Maybe ^ForwardParams)
  = MinterUnlockPayload;

htlc_dutch_try_unlock_args#_
  minOut:Grams
  dutchIgnoreRefundAmount:Grams
  dutchSignature:(Maybe bits512)
  = HtlcDutchTryUnlockArgs;

Field Descriptions

MinterUnlockPayload

  • quoteId: Must match the locked escrow's quoteId.

  • resolverFillToVault: Whether resolver's received tokens go to the vault (true) or direct (false).

  • resolverRefundToVault: Whether overpayment refund goes to the vault (true) or direct (false).

  • excesses: Address to receive excess TON and jetton refunds.

  • unlockArgs: Serialized HtlcDutchTryUnlockArgs.

  • forwardParams: Optional forward parameters for notifications.

HtlcDutchTryUnlockArgs

  • minOut: Slippage protection – minimum offer tokens to receive; reverts if toReceive < minOut.

  • dutchIgnoreRefundAmount: Gas optimization – skip refund if overpayment is below this threshold.

  • dutchSignature: Required signature if dutchResolverPublicKey is set; verified against the resolver address hash.

Unlock Phases

Phase 1: Resolver Period (now ≤ resolverTimeout)

Behavior

  • Only the designated resolver can unlock.

  • Must send exactly resolverAskAmount of ask jettons.

  • Receives the entire initOfferAmount of offer tokens.

  • Sets dutchRemaining to 0 (fully filled).

Validations

  • msg.resolver == resolverData.resolver

  • msg.resolverSentAmount == resolverAskAmount

Phase 2: Dutch Auction (resolverTimeout < now < dutchTimeout)

Behavior

  • Anyone can unlock (subject to signature if required).

  • Price decreases linearly: dutchPrice = (resolverAskAmount * (dutchTimeout - now) + dutchMinAskAmount * (now - resolverTimeout)) / (dutchTimeout - resolverTimeout)

  • Partial fills allowed – multiple unlocks can occur until dutchRemaining == 0.

  • Overpayment is refunded if it exceeds dutchIgnoreRefundAmount.

Calculation

  • toReceive = floor(initOfferAmount * resolverSentAmount / dutchPrice)

  • If toReceive > dutchRemaining, cap at remaining and refund excess ask jettons.

  • Update: dutchRemaining -= toReceive

Validations

  • If dutchResolverPublicKey is set, verify dutchSignature against resolver address hash.

  • now < dutchTimeout

  • dutchRemaining - toReceive >= 0

  • minOut <= toReceive

Phase 3: Expired (now ≥ dutchTimeout)

Order cannot be unlocked. User must call cancel to refund tokens.

Common Validations

  • resolverSentJettonWallet must equal askJettonWallet.

  • initOfferAmount must be > 0 (should never fail unless state is corrupted).

  • Unlock resolver address must be in basechain (workchain 0).


Cancel Operation

When to Use

Refund locked tokens to the original user. Typically after the Dutch auction expires or by user request.

Behavior

  • Sets both timeouts to the current time (making the order immediately expired).

  • Resets dutchRemaining to initOfferAmount (unfills any partial fills).

  • Does not transfer tokens – handled by the Item contract after cancel succeeds.

  • Can be called by the owner at any time.

Effect

Subsequent unlock attempts fail with VM_EXIT_STATUS_DUTCH_TIMEOUT_EXPIRED.


Error Codes

Code
Name
Description

VM_EXIT_STATUS_RESOLVER_TIMEOUT_IS_ZERO

Resolver timeout zero

Lock failed: resolverTimeout must be > 0

VM_EXIT_STATUS_RESOLVER_ASK_AMOUNT_IS_ZERO

Resolver ask amount zero

Lock failed: resolverAskAmount must be > 0

VM_EXIT_STATUS_DUTCH_MIN_ASK_NOT_LESS_THAN_RESOLVER_ASK

Invalid price range

Lock failed: dutchMinAskAmount must be < resolverAskAmount

VM_EXIT_STATUS_ASK_JETTON_WALLET_WRONG_WORKCHAIN

Invalid ask wallet workchain

Lock failed: askJettonWallet must be in basechain

VM_EXIT_STATUS_RESOLVER_WRONG_WORKCHAIN

Invalid resolver workchain

Lock failed: resolver address must be in basechain

VM_EXIT_STATUS_WRONG_RESOLVER_SENT_JETTON_WALLET

Wrong jetton

Unlock failed: sent wrong jetton type

VM_EXIT_STATUS_INIT_OFFER_AMOUNT_IS_ZERO

Offer amount zero

Unlock failed: initOfferAmount is zero (corrupted state)

VM_EXIT_STATUS_UNLOCK_RESOLVER_WRONG_WORKCHAIN

Invalid unlock resolver workchain

Unlock failed: resolver address must be in basechain

VM_EXIT_STATUS_RESOLVER_MISMATCH

Unauthorized resolver

Unlock failed during resolver period: wrong resolver address

VM_EXIT_STATUS_RESOLVER_ASK_AMOUNT_MISMATCH

Wrong amount

Unlock failed during resolver period: must send exact resolverAskAmount

VM_EXIT_STATUS_INVALID_SIGNATURE

Invalid signature

Unlock failed during Dutch: signature verification failed

VM_EXIT_STATUS_DUTCH_TIMEOUT_EXPIRED

Auction expired

Unlock failed: auction ended, use cancel

VM_EXIT_STATUS_DUTCH_REMAINING_NEGATIVE

Negative remaining

Unlock failed during Dutch: arithmetic error, remaining would be negative

VM_EXIT_STATUS_MIN_OUT_EXCEEDS_TO_RECEIVE

Slippage exceeded

Unlock failed: received less than minOut


Usage Examples

Example 1: Lock 100 STON for 50 USDT (Resolver) or 30–50 USDT (Dutch)

// Lock parameters
const lockParams: HtlcDutchLockParams = {
    initOfferAmount: 100_000000n, // 100 STON
    askJettonWallet: usdtWalletAddress,
    askJettonMinter: usdtMinterAddress,
    resolverData: {
        resolver: trustedResolverAddress,
        resolverTimeout: 3600n, // 1 hour exclusive
        resolverAskAmount: 50_000000n, // 50 USDT fixed price
    },
    dutchData: {
        dutchRemaining: 100_000000n,
        dutchMinAskAmount: 30_000000n, // 30 USDT floor
        dutchTimeout: 7200n, // 2 hours Dutch after resolver period
        dutchResolverPublicKey: null, // anyone can fill during Dutch
    },
};

// Send STON with MinterLockPayload

Example 2: Unlock During Dutch Auction

// At 4500 seconds after lock (1.5 hours = mid-Dutch)
// Price is 40 USDT (linear interpolation between 50 and 30)
// Sending 20 USDT gets: floor(100 * 20 / 40) = 50 STON

const unlockArgs: HtlcDutchTryUnlockArgs = {
    minOut: 48_000000n, // accept >= 48 STON (slippage tolerance)
    dutchIgnoreRefundAmount: 100000n, // skip refund if < 0.1 USDT overpaid
    dutchSignature: null, // no signature required
};

// Send 20 USDT with MinterUnlockPayload
// Receive 50 STON, dutchRemaining becomes 50 STON

Example 3: Resolver Fills Entire Order

// Within first hour
const unlockArgs: HtlcDutchTryUnlockArgs = {
    minOut: 0n,
    dutchIgnoreRefundAmount: 0n,
    dutchSignature: null,
};

// Resolver sends exactly 50 USDT
// Receives full 100 STON, dutchRemaining becomes 0

  • Contact the STON.fi team for partner onboarding or integration support.

Last updated