Developer Reference

This page documents the on-chain interface of DinoSecondary, the secondary-market module of the DINO stack.

Each DinoSecondary instance is bound to one T-REX (ERC-3643) token and exposes a simple offer book where users can post and take bilateral offers between that T-REX token and any ERC-20 payment token. The contract supports:

  • Freeze-based handling for the ERC-3643 leg (tokens locked in the investor’s wallet)

  • Escrow-based handling for the non-ERC-3643 leg (tokens held by the contract)

  • Fixed and volume-based fees

  • Ecosystem-level fees (handled by the T-REX fee collector)

This page is aimed at dapp and integration developers. Admin-only concerns (roles, pausing, fee configuration) are covered in the Admin & Management page.

Contract Surface

At a high level, the public interface exposed to integrators is:

interface IDinoSecondary {
    struct Fee {
        address feeToken;
        uint256 feeAmount;
        address feeCollector;
    }

    enum OfferStatus {
        Active,
        Filled,
        Cancelled,
        Expired
    }

    struct Offer {
        address offerCreator;
        address tokenOut;
        uint256 amountOut;
        uint256 filledAmountOut;
        address tokenIn;
        uint256 amountIn;
        uint256 filledAmountIn;
        uint256 expiry;
        OfferStatus status;
        uint256 createdAt;
    }

    function createOffer(
        address tokenOut,
        uint256 amountOut,
        address tokenIn,
        uint256 amountIn,
        uint256 expiry
    ) external returns (uint256 offerID);

    function takeOffer(uint256 offerID, uint256 amount) external;
    function cancelOffer(uint256 offerID) external;
    function pruneOffer(uint256 offerID) external;

    function setFixedFee(address feeToken, address feeRecipient, uint256 amount) external;
    function setVolumeBasedFee(address feeRecipient, uint256 basisPoints) external;

    function TREX() external view returns (address);

    function fixedFee() external view returns (Fee memory);
    function volumeFee() external view returns (Fee memory);

    function getOfferExpiry(uint256 offerID) external view returns (uint256);
    function getCurrentOfferID() external view returns (uint256);
    function getOfferDetails(uint256 offerID) external view returns (Offer memory);
}

Note: The implementation also integrates AccessManager roles, pausing and ecosystem fee collection. Those are configured via the factory and documented in the DinoFactory and Admin pages.

Core Data Structures

Fee

Represents a fee configuration used by DinoSecondary:

Field
Type
Description

feeToken

address

Token in which the fee is paid (ERC-20).

feeAmount

uint256

Meaning depends on context: fixed amount (for fixed fee) or basis points (for volume fee).

feeCollector

address

Address receiving the fee.

  • For fixed fees, feeAmount is the exact amount of feeToken charged per takeOffer.

  • For volume-based fees, feeAmount represents the basis points (bps) applied on the non-ERC-3643 leg, capped at 100 bps (1%).

OfferStatus

Offer lifecycle states:

  • Active – Offer is live and can be taken.

  • Filled – Offer is fully executed.

  • Cancelled – Cancelled by creator.

  • Expired – Expired based on expiry timestamp and/or pruned.

Offer

Represents a single order in the book:

Field
Type
Description

offerCreator

address

Maker address.

tokenOut

address

Token being sold (outgoing from maker).

amountOut

uint256

Total amount offered for sale.

filledAmountOut

uint256

Cumulative portion of amountOut that has been taken.

tokenIn

address

Token expected in exchange.

amountIn

uint256

Total amount expected in the counter-token.

filledAmountIn

uint256

Cumulative portion of amountIn that has been received.

expiry

uint256

UNIX timestamp after which offer is no longer tradable.

status

OfferStatus

Current status (Active/Filled/Cancelled/Expired).

createdAt

uint256

Creation timestamp.

Amounts always keep the original price ratio: partial fills are proportional and update the filledAmount* fields accordingly.

Trading Functions

createOffer

function createOffer(
    address tokenOut,
    uint256 amountOut,
    address tokenIn,
    uint256 amountIn,
    uint256 expiry
) external returns (uint256 offerID);

Purpose

Create a new offer to sell tokenOut for tokenIn at a fixed ratio amountIn / amountOut.

High-level flow

  1. Caller checks prerequisites and approves tokens for DinoSecondary (for the ERC-20 leg).

  2. Contract validates:

    • amountOut and amountIn are non-zero.

    • expiry is in the future.

    • At least one leg is the ERC-3643 token bound to this DinoSecondary instance.

  3. Token handling:

    • If the ERC-3643 token is being sold: tokens are frozen in the maker’s wallet (not transferred).

    • If the ERC-20 token is being sold: tokens are moved into escrow (held by the contract).

  4. Offer struct is stored and a unique offerID is assigned (monotonically increasing).

  5. Ecosystem fee is charged via the T-REX fee collector.

  6. OfferCreated event is emitted.

Common errors

  • ZeroAmount() – if one of the amounts is 0.

  • InvalidExpiry(expiry) – if expiry is in the past or otherwise rejected.

  • InvalidToken(token) – if either token address is not allowed.

takeOffer

function takeOffer(uint256 offerID, uint256 amount) external;

Purpose

Accept an existing offer (partially or fully) and execute the trade.

High-level flow

  1. Validates that:

    • offerID refers to an existing offer.

    • Offer is Active and not expired.

    • amount is > 0.

  2. Computes the proportional amountIn based on original price ratio.

  3. Token transfers:

    • ERC-3643 leg:

      • If maker is selling ERC-3643, previously frozen tokens are unfrozen and transferred to the taker.

      • If taker is selling ERC-3643, tokens move via standard transferFrom logic to the maker.

    • ERC-20 leg: tokens move via standard transferFrom/escrow logic.

  4. Fees:

    • Volume-based fee applied on the non-ERC-3643 leg in basis points (volumeFee().feeAmount).

    • Fixed fee charged to the taker in fixedFee().feeToken and sent to fixedFee().feeCollector.

    • Ecosystem fee collected via the fee collector.

  5. Offer state updated:

    • filledAmountOut / filledAmountIn updated.

    • Status set to Filled if fully executed; otherwise remains Active.

  6. Events:

    • OfferPartiallyFilled if there is remaining liquidity.

    • OfferFilled if fully executed.

Common errors

  • InvalidOfferId(offerID) – offer does not exist.

  • ZeroAmount() – taker tries to take 0.

  • InvalidTokenTransfer(token) – failure in underlying token transfer.

  • VolumeFeeExceedsAmount(volumeFee, amount) – volume-based fee would be larger than the traded amount.

cancelOffer

function cancelOffer(uint256 offerID) external;

Purpose

Allow the offer creator to cancel an active offer.

Behavior

  • Only the original offerCreator can cancel their own offers.

  • Any frozen ERC-3643 tokens are released.

  • Any ERC-20 tokens held in escrow are returned to the maker.

  • Status is set to Cancelled.

  • OfferCancelled event is emitted.

Common errors

  • InvalidOfferId(offerID) – no such offer.

  • OfferNotCreatedBySender(offerID, sender) – caller is not the original maker.

pruneOffer

function pruneOffer(uint256 offerID) external;

Purpose

Clean up offers that are no longer valid, for example:

  • Expired offers.

  • Offers that became non-compliant.

  • Offers where the ERC-3643 leg is no longer funded (maker removed allowance or an agent moved away the frozen balance reserved for the transfer).

Pruning is open to any caller; it updates status to Expired and emits OfferPruned. If tokens had been locked/frozen, they are freed accordingly.

Common errors

  • InvalidOfferId(offerID) – no such offer.

Fee Configuration & Getters

setFixedFee

function setFixedFee(address feeToken, address feeRecipient, uint256 amount) external;

Admin-only function to configure the per-transaction fixed fee:

  • feeToken: ERC-20 token used to pay the fee.

  • feeRecipient: address that receives fixed fees.

  • amount: exact amount of feeToken charged on each takeOffer.

Reverts with:

  • VolumeBasedFeeTooHigh does not apply here.

  • May revert with ZeroAddress (common error) if token or recipient is zero.

setVolumeBasedFee

function setVolumeBasedFee(address feeRecipient, uint256 basisPoints) external;

Admin-only function to configure the percentage-based fee:

  • basisPoints: rate in bps (100 = 1%) applied to the non-ERC-3643 leg of each trade.

  • basisPoints is capped at 100; higher values revert with VolumeBasedFeeTooHigh.

Fee getters

function fixedFee() external view returns (Fee memory);
function volumeFee() external view returns (Fee memory);
  • fixedFee()

    • feeToken: token used for the fixed fee.

    • feeAmount: fixed fee amount.

    • feeCollector: address receiving fixed fees.

  • volumeFee()

    • feeToken: not used for calculations (bps is applied on the trade amount).

    • feeAmount: basis points value (0–100).

    • feeCollector: address receiving the volume-based fee.

The implementation also provides convenience view functions for the raw recipients (fixedFeeRecipient, volumeFeeRecipient) as documented in the DinoSecondary docs.

For the ecosystem fee, see DinoFactory / Global Fees docs – it is collected separately by the T-REX Ecosystem Fee Collector.

Read-Only Helpers

TREX

function TREX() external view returns (address);

Returns the ERC-3643 token address this DinoSecondary instance is bound to. Every offer must involve this token in at least one leg.

getOfferExpiry

function getOfferExpiry(uint256 offerID) external view returns (uint256);

Shortcut to read only the expiry field for a given offer.

getCurrentOfferID

function getCurrentOfferID() external view returns (uint256);

Returns the next offer ID that will be assigned on the next createOffer call (i.e. a counter, not the last valid ID). To iterate over all offers, index from 0 (or 1, depending on deployment conventions) up to getCurrentOfferID() - 1, combined with getOfferDetails and event history.

getOfferDetails

function getOfferDetails(uint256 offerID) external view returns (Offer memory);

Returns the full Offer struct for the given offerID. Use this for:

  • Front-end display of the order book.

  • Reconciling with your indexer state.

  • Checking status, amounts and timestamps on-chain.

Events

DinoSecondary uses a dedicated events library for consistent logging.

Trading events

Event
When it fires
Key fields

OfferCreated(uint256 offerID, address offerCreator, address tokenOut, uint256 amountOut, address tokenIn, uint256 amountIn, uint256 creationTime, uint256 expiry)

A new offer is created

offerID, maker, tokens and amounts, timestamps

OfferPartiallyFilled(uint256 offerID, uint256 amount, uint256 remainingAmount, address taker)

An offer is filled but liquidity remains

amount filled in this trade, remaining, taker

OfferFilled(uint256 offerID, address taker)

Offer is fully filled

Final taker

OfferCancelled(uint256 offerID)

Maker cancels the offer

offerID

OfferPruned(uint256 offerID)

Offer is administratively pruned

offerID

Fee events

Event
Description

FixedFeeSet(address feeToken, address feeRecipient, uint256 amount)

Fixed fee config updated.

VolumeBasedFeeSet(address feeRecipient, uint256 basisPoints)

Volume-based fee config updated.

FixedFeePaid(address taker, address collector, uint256 amount)

Fixed fee paid on a takeOffer.

VolumeFeePaid(address feePayer, address collector, uint256 amount)

Volume-based fee paid on a trade.

Pausable & AccessManager events

From inherited contracts:

  • Paused(address account) / Unpaused(address account) from Pausable.

  • AuthorityUpdated(address authority) from AccessManaged when the AccessManager is changed.

These are particularly useful for indexers and monitoring dashboards.

Error Reference

Error
Typical cause

InvalidExpiry(uint256 expiry)

Expiry not acceptable (e.g. in the past).

InvalidOfferId(uint256 offerID)

Referenced offer does not exist.

InvalidToken(address token)

Token not accepted for trading.

InvalidTokenTransfer(address token)

Transfer or freeze/unfreeze failed for this token.

LimitsArraySizeExceeded(uint256 arraySize, uint256 maxSize)

(Used for batched operations, if exposed.)

OfferNotCreatedBySender(uint256 offerID, address sender)

Someone other than the maker tried to cancel.

VolumeBasedFeeTooHigh(uint256 basisPoints, uint16 maxBasisPoints)

Attempt to set volume fee above 100 bps.

VolumeFeeExceedsAmount(uint256 volumeFee, uint256 amount)

Computed volume fee greater than traded amount.

ZeroAmount()

One of the amounts is zero.

ZeroAddress()

Critical address parameter is zero.

You should surface these errors in your dapp for clearer UX (e.g. mapping them to human-readable messages).

Multicall Support

The contract supports OpenZeppelin Multicall, allowing you to batch several calls into a single transaction (for example, multiple takeOffer operations or a takeOffer + off-chain accounting update).

This is especially useful for:

  • Wallets wanting to group multiple fills.

  • Protocols executing rebalancing strategies that span several offers.

Integration Tips

  • Use TREX() to ensure you are interacting with the right DinoSecondary instance for a given asset. IDinoSecondary

  • Index events (OfferCreated, OfferPartiallyFilled, OfferFilled, OfferCancelled, OfferPruned) as your primary source of truth, and reconcile with getOfferDetails for on-chain verification.

  • Never assume offers are contiguous or permanent: always be ready for cancellation, pruning and expiry.

  • For UX, guide users through approvals (ERC-20) and highlight when the freeze path vs escrow path applies.

Last updated