Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Templar Protocol User Guide

This is a comprehensive user guide for the Templar Protocol smart contracts.

For definitions of key terms and concepts, refer to the Glossary.

Quick Navigation

Market Operations

Additional Resources

Smart Contracts

Market

A single Templar market represents a pair of collateral and borrow assets, such as BTC/USDC for Bitcoin-collateralized USDC loans.

Suppliers may deposit borrow assets into the market, and their funds will earn yield from the protocol fees paid by borrowers. Borrowers may borrow available supply assets from the market, paying a variable interest rate based on the supply utilization rate.

Markets support NEAR fungible asset contracts implementing the NEP-141 standard or the NEP-245 standard. The borrow and collateral assets do not need to implement the same standard.

Storage Management

Before interacting with a market (supplying, borrowing, depositing collateral, etc.), accounts must first register with the market contract by making a storage deposit. This is required because the market contract implements NEP-145 (Storage Management) to cover the storage costs of maintaining user positions on-chain.

Making a Storage Deposit

To register with a market and deposit the required storage cost:

near contract call-function as-transaction \
    <market-id> storage_deposit \
    json-args '{}' \
    prepaid-gas '10.0 Tgas' \
    attached-deposit '0.00125 NEAR' \
    sign-as <account-id>

The minimum required deposit can be obtained by calling the storage_balance_bounds function:

near contract call-function as-read-only \
    <market-id> storage_balance_bounds \
    json-args '{}' \
    network-config mainnet \
    now

Note: This storage deposit step is handled automatically in the Templar frontend application, but must be done manually when interacting directly with the market contracts.

Interactions

Accounts can interact with markets in seven primary ways:

  1. Deposit supply
  2. Withdraw supply
  3. Deposit collateral
  4. Withdraw collateral
  5. Borrow supply
  6. Repay supply
  7. Liquidate borrow position

Configuration

A market's configuration is immutable after deployment. It can be obtained from the market contract by calling the get_configuration function.

Example

near contract \
    call-function as-read-only ibtc-usdc.v1.tmplr.near get_configuration \
    json-args {} \
    network-config mainnet \
    now
Output
{
  "borrow_asset": {
    "Nep141": "17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1"
  },
  "borrow_asset_maximum_usage_ratio": "0.99000000000000000000000000000000000001",
  "borrow_interest_rate_strategy": {
    "Piecewise": {
      "base": "0",
      "optimal": "0.90000000000000000000000000000000000001",
      "rate_1": "0.08888888888888888888888888888888888889",
      "rate_2": "2.40000000000000000000000000000000000001"
    }
  },
  "borrow_maximum_duration_ms": null,
  "borrow_mcr_liquidation": "1.19999999999999999999999999999999999999",
  "borrow_mcr_maintenance": "1.25",
  "borrow_origination_fee": {
    "Proportional": "0.00099999999999999999999999999999999999"
  },
  "borrow_range": {
    "maximum": null,
    "minimum": "1"
  },
  "collateral_asset": {
    "Nep245": {
      "contract_id": "intents.near",
      "token_id": "nep141:btc.omft.near"
    }
  },
  "liquidation_maximum_spread": "0.05000000000000000000000000000000000001",
  "price_oracle_configuration": {
    "account_id": "pyth-oracle.near",
    "borrow_asset_decimals": 6,
    "borrow_asset_price_id": "eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a",
    "collateral_asset_decimals": 8,
    "collateral_asset_price_id": "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43",
    "price_maximum_age_s": 60
  },
  "protocol_account_id": "revenue.tmplr.near",
  "supply_range": {
    "maximum": null,
    "minimum": "40000"
  },
  "supply_withdrawal_fee": {
    "behavior": "Fixed",
    "duration": "0",
    "fee": {
      "Flat": "0"
    }
  },
  "supply_withdrawal_range": {
    "maximum": null,
    "minimum": "40000"
  },
  "time_chunk_configuration": {
    "BlockTimestampMs": {
      "divisor": "600000"
    }
  },
  "yield_weights": {
    "static": {
      "revenue.tmplr.near": 1,
      "rewards.tmplr.near": 1
    },
    "supply": 1
  }
}

Snapshots

Interest and yield on borrow and supply positions are calculated using a snapshot system.

After a "time chunk" (e.g. 1 hour) elapses, the contract takes a snapshot, recording such things as the total supply deposit, amount borrowed, timestamp, etc.

Whenever a borrow or supply position update requires, interest/yield calculations are triggered. (They can also be triggered explicitly using harvest_yield and apply_interest.) These calculations iterate from the snapshot at which the record was last updated until the most-recently-finalized snapshot unless a snapshot limit is provided.

Supply

Accounts may deposit assets to the market's supply to earn yield.

Deposit

To add funds to a market's supply, send the to the contract, specifying "Supply" as the transfer msg.

For example:

near contract call-function as-transaction \
    <borrow-asset-contract-id> ft_transfer_call \
    json-args '{
        "receiver_id": "<market-id>",
        "amount": "<amount>",
        "msg": "\"Supply\""
    }' \
    prepaid-gas '100.0 Tgas' \
    attached-deposit '1 yoctoNEAR' \
    sign-as <account-id>

Withdraw

Since borrowers borrow the assets that suppliers have supplied, when a supplier wishes to withdraw their supply, there might not be enough available to withdraw at that time. However, through fees, interest, etc., as time passes, borrow assets should become available to withdraw again.

Because the market may not have sufficient borrow asset liquidity when a supplier wishes to withdraw, the market uses a queue-based withdrawal system.

In order to withdraw supply from the market, a supplier must first enter the supply withdrawal queue with their withdrawal request:

near contract call-function as-transaction \
    <market-id> create_supply_withdrawal_request \
    json-args '{"amount": "<amount>"}' \
    prepaid-gas '100.0 Tgas' \
    attached-deposit '0 NEAR' \
    sign-as <account-id>

Now the account has a position in the queue. Should the account wish to update the amount of the request, it can call create_supply_withdrawal_request again, however, this will also reset its position to the end of the queue.

A supply withdrawal request can be cancelled via cancel_supply_withdrawal_request.

In order for an account's supply withdrawal request to be fulfilled, all of the requests that are ahead of it in the queue must be fulfilled first.

To execute the next withdrawal request, use the execute_next_supply_withdrawal_request function:

near contract call-function as-transaction \
    <market-id> execute_next_supply_withdrawal_request \
    json-args '{}' \
    prepaid-gas '100.0 Tgas' \
    attached-deposit '0 NEAR' \
    sign-as <account-id>

This function is not permissioned; anyone may call it to advance the withdrawal queue.

Borrow

Accounts may borrow assets from the market's supply.

Borrow positions must be collateralized with a minimum amount of collateral asset determined by the market's configuration.

Deposit collateral

To add collateral to an account's position, transfer-call the market tokens with a msg of "Collateralize":

near contract call-function as-transaction \
    <collateral-asset-contract-id> ft_transfer_call \
    json-args '{
        "receiver_id": "<market-id>",
        "amount": "<amount>",
        "msg": "\"Collateralize\""
    }' \
    prepaid-gas '100.0 Tgas' \
    attached-deposit '1 yoctoNEAR' \
    sign-as <account-id>

Withdraw collateral

The collateral withdrawal process is relatively straightforward as compared to the supply withdrawal process: simply call withdraw_collateral, passing the amount of collateral asset tokens you wish to withdraw:

near contract call-function as-transaction \
    <market-id> withdraw_collateral \
    json-args '{ "amount": "<amount>" }' \
    prepaid-gas '100.0 Tgas' \
    attached-deposit '0 NEAR' \
    sign-as <account-id>

While this process is simple, collateral can only be withdraw so long as the value of the remaining collateral continues to satisfy the market's borrow_mcr_maintenance requirement.

Borrow

Once an account's position is collateralized, borrow asset can be withdrawn.

near contract call-function as-transaction \
    <market-id> borrow \
    json-args '{ "amount": "<amount>" }' \
    prepaid-gas '100.0 Tgas' \
    attached-deposit '0 NEAR' \
    sign-as <account-id>

As long as the collateralization requirements are met, the borrow amount (minus fees) will be sent to the predecessor account.

Repay

As long as an account has a liability (principal + interest/fees), some or all of their collateral will be locked so that it cannot be withdrawn.

To unlock the collateral, the account must repay its liability to the market.

To perform a repayment, transfer-call tokens to the market with a msg of "Repay":

near contract call-function as-transaction \
    <borrow-asset-contract-id> ft_transfer_call \
    json-args '{
        "receiver_id": "<market-id>",
        "amount": "<amount>",
        "msg": "\"Repay\""
    }' \
    prepaid-gas '100.0 Tgas' \
    attached-deposit '1 yoctoNEAR' \
    sign-as <account-id>

Liquidate

Liquidation is the process by which the asset collateralizing certain positions may be reappropriated e.g. to recover assets for an undercollateralized position.

A liquidator is a third party willing to send a quantity of a market's borrow asset (usually a stablecoin) to the market in exchange for the amount of collateral asset supporting a specific account's position. As compensation for this service, the liquidator receives an exchange rate that is slightly better than the current rate. This difference in rates is called the "liquidator spread," and the maximum liquidator spread is configurable on a per-market basis.

  • The liquidator MUST ensure that its account is able to receive the collateral tokens. Usually this means opting-in to storage management (if the collateral token in question implements that standard). If the collateral transfer fails, the liquidator will not be refunded!
  • It is the responsibility of the liquidator to calculate the optimal amount of tokens to attach to a liquidation call. The market will either completely accept or completely reject the liquidation attempt—no refunds!

A liquidator will follow this high-level workflow:

  1. The liquidator obtains a list of accounts borrowing from the market by calling list_borrow_positions.
  2. The liquidator checks the status of each account by calling get_borrow_status.
  3. If an account's status is Liquidation, that means the liquidator can obtain a spread by sending an amount of borrow asset to the market. The maximum spread is liquidation_maximum_spread in the market configuration.
  4. To perform the liquidation, the liquidator transfer-calls the appropriate amount of borrow asset to the market. That is to say, the liquidator calls ft_transfer_call/mt_transfer_call on the borrow asset's smart contract, specifying the market as the receiver. The msg parameter indicates 1) that the transfer is for a liquidation, and 2) which account is to be liquidated.

Thus, the arguments to a liquidation call might look something like this:

{
  "amount": "<amount>",
  "msg": {
    "Liquidate": {
      "account_id": "<account-to-liquidate>"
    }
  },
  "receiver_id": "<market-id>"
}

Example

near contract call-function as-transaction \
    <borrow-asset-contract-id> ft_transfer_call \
    json-args '{
        "receiver_id": "<market-id>",
        "amount": "<amount>",
        "msg": "{ \"Liquidate\": { \"account_id\": \"<account-to-liquidate>\" } }"
    }' \
    prepaid-gas '100.0 Tgas' \
    attached-deposit '1 yoctoNEAR' \
    sign-as <account-id>

Registry

The registry is a contract that maintains a list of contract versions, deploys new contracts, and maintains a list of those deployments.

Market contracts are deployed through a registry, and thus appear as its subaccounts.

The account ID of the mainnet market registry is v1.tmplr.near.

Interactions

List available versions

near contract call-function as-read-only \
    v1.tmplr.near list_versions \
    json-args '{"offset":0,"count":100}' \
    network-config mainnet \
    now

Output:

[
  "v1.0.0",
  "v1.1.0"
]

List deployments

near contract call-function as-read-only \
    v1.tmplr.near list_deployments \
    json-args '{"offset":0,"count":100}' \
    network-config mainnet \
    now

Output:

[
  "ibtc-usdc.v1.tmplr.near",
  "stnear-usdc.v1.tmplr.near",
  "ibtc-iethusdc.v1.tmplr.near",
  "iethwbtc-iethusdc.v1.tmplr.near",
  "ibtc-usdc-1.v1.tmplr.near",
  "stnear-usdc-1.v1.tmplr.near"
]

LST Oracle

The LST oracle adapter enhances base oracle functionality by supporting a broader range of asset classes. The primary transformation supported by the LST oracle adapter is price normalization of a liquid staking token (LST).

Price normalization requires retrieving the price of the underlying asset and the conversion rate between the LST and the underlying asset and combining them to produce a price for the LST asset itself.

Example: stNEAR Price Calculation

Examine the transformer specification:

near contract call-function as-read-only \
    lst.oracle.tmplr.near get_transformer \
    json-args '{"price_identifier":"c23cb2430c81d475fbd1c235324d4987f2dd01431bf7ab3e7b9d69b9f6701470"}' \
    network-config mainnet \
    now

Output:

{
  "action": {
    "NormalizeNativeLstPrice": {
      "decimals": 24
    }
  },
  "call": {
    "account_id": "meta-pool.near",
    "args": "bnVsbA==",
    "gas": "3000000000000",
    "method_name": "get_st_near_price"
  },
  "price_id": "c415de8d2eba7db216527dff4b60e8f3a5311c740dadb233e13e12547e226750"
}

This specification describes the following flow:

  1. Retrieve the NEAR price from the Pyth oracle (asset ID c415de8d2eba7db216527dff4b60e8f3a5311c740dadb233e13e12547e226750).
  2. Retrieve the stNEAR redemption rate from the staking contract (meta-pool.near->get_st_near_price({})).
  3. Calculate price_stnear = price_near * redemption_rate / 10^24.

Smart Contract Addresses

Deployed Templar Protocol contracts, and how to verify them. For deploying a new market, see Deploying a market.

Deployments

Markets

Market contracts are deployed dynamically through the registry. Each market represents a single asset pair (COLLATERAL → BORROW).

A selection of available markets is shown below:

Account IDCollateral AssetBorrow Asset
ibtc-iethusdc.v1.tmplr.nearNative BTC (via NEAR Intents)USDC on Ethereum (via NEAR Intents)
iethwbtc-iethusdc.v1.tmplr.nearwBTC on Ethereum (via NEAR Intents)USDC on Ethereum (via NEAR Intents)
ibtc-usdc-1.v1.tmplr.nearNative BTC (via NEAR Intents)USDC on NEAR
stnear-usdc-1.v1.tmplr.nearstNEAR on NEARUSDC on NEAR
ixlm-ixlmusdc.v1.tmplr.nearNative XLM (via NEAR Intents)USDC on XLM (via NEAR Intents)

Contract Verification

All smart contracts use reproducible builds. To verify deployed code:

near contract verify deployed-at <contract-id> mainnet now

Example output:

INFO The code obtained from the contract account ID and the code calculated from the repository are the same.
|    Contract code hash: DaudmUa3nAym9dfQkn8mpNPZxkphSRGwEaTMgtymVhFE
|    Contract version:	1.0.0
|    Standards used by the contract:	[nep330:1.2.0]
|    View the contract's source code on:	https://github.com/Templar-Protocol/contracts/tree/1d736e62a86424dd947284cbd8e83bef803fa9fb
|    Build Environment:	sourcescan/cargo-near:0.13.4-rust-1.85.0@sha256:a9d8bee7b134856cc8baa142494a177f2ba9ecfededfcdd38f634e14cca8aae2
|    Build Command:	cargo near build non-reproducible-wasm --locked

Deploying a market

A market is described by one spec file and deployed in two commands. There is no shell script: the spec is the source of truth, and everything that used to live in env.sh, market-args.json and proxy-*.json is derived from it.

deployments/alpha targets a mainnet registry, so both commands need --network mainnet — the CLI defaults to testnet. NETWORK and SIGNER_ID work in place of the flags.

tmplrmgr market plan  deployments/alpha/<market>.toml --out plan.json \
    --network mainnet --signer-id "$REGISTRY_OWNER" --public-key ed25519:…
tmplrmgr market apply --plan plan.json \
    --network mainnet --signer-id "$REGISTRY_OWNER" --sign-with keychain

The signer is not a personal account: registry.deploy asserts the registry's owner, and a proxy spec additionally requires it to equal governance.admin, which the mainnet profiles set to the registry itself.

plan reads the chain and writes a file; it sends nothing and takes no credential. apply sends what the file says.

Why two steps

The plan is a reviewable artifact. It lists every transaction, decodes the market configuration for reading, names the keys each new account will grant, and carries the results of every preflight check. Reviewing a deployment no longer means reading a shell script and trusting it matches four JSON files.

It is a record of a derivation, not an input: the file carries the spec it came from, and apply re-derives the steps and refuses anything that does not match. Editing the plan is therefore not a way to change a deployment — change the spec and re-plan. For something no spec can express, run the transaction yourself with the command that performs it (registry deploy, proxy-oracle governance create-proposal, storage deposit); each is typed and validated on its own.

Writing a spec

Shared values live in deployments/profiles/. A market file names the profiles it extends and states only what differs. Abbreviated — a market also needs a [borrow] leg and the [market] parameters the profiles above do not set; see any file under deployments/ for a complete one:

extends = ["../profiles/alpha.toml", "../profiles/irs-standard.toml"]
name = "my-market"

[oracle.direct]                    # reads an oracle that already exists
account_id = "pyth-oracle.near"

[collateral]
asset = "nep141:usdc.near"
price_id = "eaa020c6…"             # the oracle's own identifier
decimals = 6

Omit [oracle.direct] to deploy a dedicated proxy oracle instead. A proxy market names sources per asset and the deployment creates a governance contract, the oracle it owns, and the market — seven transactions, plus one storage registration per NEP-141 asset, rather than one.

Amounts

Every amount states its unit, and the tool does the scaling:

[market]
borrow_range            = { minimum = "1 atom" }
supply_range            = { minimum = "0.04 tokens" }
supply_withdrawal_range = { minimum = "0.04 tokens", maximum = "1000 tokens" }
origination_fee         = { Flat = "0 atoms" }

tokens counts whole units of the borrow asset, scaled by its decimals when the plan is built — "0.04 tokens" is four cents of a stablecoin whether it carries 6 decimals or 7. atoms counts the indivisible base units the chain stores, so "1 atom" says "no real floor" in a way "0.0000001 tokens" does not. All three ranges and both fees are denominated in the borrow asset; nothing is stated in collateral.

The unit is mandatory. A bare number is refused rather than guessed at, as is a tokens value with more decimal places than the asset can hold, or a fractional atom. Both spellings parse (1 atom, 1 atoms); the tool writes the plural.

This is what schema 5 changed. A schema 4 file wrote the same amounts as bare base-unit integers, which are still well-formed numbers — read as whole units they would be 10^decimals too large — so such a file is refused by version and must be re-authored, not renumbered.

Checking before and after

tmplrmgr spec check   deployments/alpha/<market>.toml --network mainnet
tmplrmgr market verify <account-id> --network mainnet \
    --governance-admin <account-id> \
    --against deployments/alpha/<market>.toml

Both modes verify. A direct market reconstructs without proxies or governance, so the two governance checks are skipped and everything else runs; --governance-admin is still required and means nothing there.

verify re-runs the preflight against what is actually on chain and exits non-zero on failure, so it can run on a schedule. That matters because the governance call that configures a price feed is dispatched detached: it reports success even when the oracle rejected the proxy, so deployed state is the only witness that a market can price anything.

Reading the report

Checks are printed to stderr as they run, grouped by what is being read, then summarized. The summary leads with the failures, in full, and lists what was skipped separately — a check that did not run proves nothing, and must never be counted as one that passed.

→ registry versions
  ok   registry.version.market          v1.3.0
  FAIL registry.version.oracle          `0.5.9` is not registered in v1.tmplr.near; the depl…

5 check(s): 3 passed, 1 skipped, 1 FAILED

FAILED
  registry.version.oracle
    `0.5.9` is not registered in v1.tmplr.near; the deploy would fail partway

Colour is used only on a terminal, and NO_COLOR turns it off. -q silences the report. stdout stays the machine-readable channel throughout, so spec check … >/dev/null leaves the report alone and … 2>/dev/null | jq leaves the JSON alone.

--skip-check <id> suppresses one verdict — every other check still runs, and the report records what the skip suppressed, so an override stays reviewable rather than reading as a pass. An id that matches no check is an error, since a typo would otherwise silently suppress nothing. Available on spec check, market plan and market apply.

Resuming

apply journals each step beside the plan as it lands. If a run is interrupted, re-running it skips what completed and continues from the first incomplete step. A plan truncated to its completed prefix is refused rather than reported complete: the re-derivation runs before the journal is consulted.

Oracles

Templar Protocol relies on external price oracles to determine asset valuations when calculating collateralization ratios and performing liquidations.

Pyth Network is the primary oracle provider (documentation).

Pyth is a pull oracle, meaning that the price feeds are updated as-needed instead of continuously. As such, interactions with Templar markets should always be preceded by a call to the appropriate oracle contract to update the necessary asset prices using a proof provided by Pyth.

More information about how to perform this update on NEAR can be found on Pyth's documentation site.

Oracle Addresses

NetworkAccount ID
Testnetpyth-oracle.testnet
Mainnetpyth-oracle.near

Price Identifiers

Price identifiers for Pyth Network assets can be found on their documentation site.

LST Oracle Adapter

For Liquid Staking Tokens (LSTs), Templar uses a custom oracle adapter (lst.oracle.tmplr.near) to derive the LST price from the underlying asset price(s).

Price Feed Configuration

Each market is configured with the following fields:

#![allow(unused)]
fn main() {
pub struct PriceOracleConfiguration {
    /// Account ID of the oracle contract.
    pub account_id: AccountId,
    /// Price identifier of the collateral asset in the oracle contract.
    pub collateral_asset_price_id: PriceIdentifier,
    /// Collateral asset decimals, to convert the oracle price.
    pub collateral_asset_decimals: i32,
    /// Price identifier of the borrow asset in the oracle contract.
    pub borrow_asset_price_id: PriceIdentifier,
    /// Borrow asset decimals, to convert the oracle price.
    pub borrow_asset_decimals: i32,
    /// Maximum price age to accept from the oracle, after which the price
    /// will be considered stale and rejected.
    pub price_maximum_age_s: u32,
}
}

Update Frequency and Freshness

  • Update Frequency: As-needed (pull model)
  • On-Chain Updates: Pulled on-demand by protocol operations
  • Price Staleness: Configurable maximum age per market (typically 60 seconds)

Price Validation

Markets validate price freshness before use. If prices are stale, users must push fresh price data to the oracle contracts

  • Operations that require prices (borrow, liquidate) will fail.
  • Users must push updates for fresh price data.

There is currently no backup oracle available.

Oracle Security Measures

  • Confidence Intervals: Pyth prices include confidence bands. The lower bound is used for collateral valuations, and the upper bound for liability valuations.
  • Multiple Data Sources: Pyth aggregates from multiple price providers.
  • Time-Weighted Averages: Market contracts use the exponentially-weighted moving average (EMA) price information.
  • Maximum Age Limits: Markets reject stale price data using a configurable expiration duration.

Oracle Failure Scenarios

Temporary Outage

  • The vast majority of operations will cease to function until fresh price data are available.
  • Users can still withdraw collateral from positions with zero liability.
  • No borrows or liquidations are supported until fresh price data are available.

Price Manipulation Attack

  • Markets will reject stale prices automatically.
  • Defensive asset valuations will protect markets from insolvency in most cases.
  • The required maintenance MCR will protect borrowers from unexpected liquidation in most cases.

Protocol Governance

This document outlines the current administrative structure and governance controls of Templar Protocol.

Registry Contract

The registry contract is immutable once deployed and locked. It designates an owner account, which has permission to add new contract code versions and to deploy new contracts. There is no upgrade mechanism.

Market Contracts

Market contracts are immutable once deployed and locked. The configuration is immutable after deployment. New market versions can be deployed to new account IDs via a registry, but old versions cannot be overwritten. There is no upgrade mechanism. When a new version of the market contract is available, it will be uploaded to the registry contract. New markets can then be deployed using the updated code. However, old markets will not be upgraded, and funds will not be automatically migrated, so users will need to migrate their positions individually.

Market contracts have no administrative functions:

  • Operate autonomously based on initial configuration.
  • No ability to pause, upgrade, or modify market parameters.

Emergency Procedures

Markets are immutable once they are deployed. If a bug is discovered, a patched version of the code will be uploaded and affected markets will have new versions deployed. However, users will need to migrate their funds individually. To facilitate this process swiftly and securely, users are encouraged to monitor all official communication channels for announcements.

Transparency and Monitoring

Templar markets are open-source, and the source code currently available on GitHub. All completed audits will be made available as soon as possible. See the current list of audits.

Stellar Vault Curator Guide

This is the operator runbook for Templar vaults on Stellar/Soroban. The tmplr-soroban-vault CLI and its deployment manifest are the supported curator interface for deployment, governance, allocation, withdrawal servicing, accounting maintenance, and TTL renewal.

Scope

The separate NEAR vault executor is not a deployed or audited curator surface and is not an operations reference for this guide. A Stellar vault adapter may represent a route that ultimately leaves Stellar, but the vault, shares, governance, accounting, and curator actions described here remain on Stellar.

Authoritative references

What a curator operates

A Templar Stellar vault is a single-asset vault with ERC-4626-compatible deposit, mint, withdraw, redeem, conversion, and limit semantics exposed through a Soroban proxy. Depositors supply one SEP-41 asset and receive transferable SEP-41 vault shares. Curators configure adapter-backed markets; allocators move pooled assets between the vault's idle balance and those markets.

A deployed stack contains:

  • Vault runtime — canonical custody, accounting, state machine, RBAC, withdrawal queue, and applied policy state.
  • Share token — the SEP-41 receipt token. Only the vault can mint and burn shares for vault flows.
  • Governance contract — proposal submission, per-action timelocks, acceptance, revocation, and irreversible abdication.
  • ERC-4626 proxy — user-friendly deposit, mint, atomic withdraw, redeem, and preview methods.
  • Curator proxy — the typed curator-facing proxy included in a full stack.
  • Adapters — one contract per market route, such as a Blend pool adapter or a custodial adapter.

The runtime uses the shared templar-vault-kernel state machine, but all operational calls in this guide target the Stellar contracts through tmplr-soroban-vault or stellar contract invoke.

Accounting model

The core invariant is:

total_assets = idle_assets + external_assets
  • idle_assets are underlying tokens held by the vault. They fund deposits, atomic exits, and queued-withdrawal payouts.
  • external_assets are the aggregate market principals/NAV recorded from adapters. The vault does not create a per-user market position.
  • Direct transfers of the underlying asset to the vault are reconciled as idle assets for existing shareholders. They are not captured by the next depositor.
  • Adapter NAV is not live-read by every preview. Run curator refresh-markets before relying on share-rate or fee-accounting views after a route's value changes.

Roles and authority

IdentityAuthority
Governance adminSubmits and accepts governance actions. This is the address passed as --admin and may be a Stellar account or a contract/multisig.
Vault curatorRuntime policy authority and an implicit allocator. A new stack uses the deployment --admin as the initial curator; governance can later replace it.
AllocatorSupplies to markets, recalls liquidity, refreshes adapter NAV, executes ready queued withdrawals, and may abort a stale Withdrawing operation.
SentinelSeparate emergency backstop. It can pause, tighten restrictions, revoke specified operational/economic proposals, and use allocator-emergency recovery. It cannot unpause, relax restrictions, or accept proposals.

The governance contract is not implicitly the Sentinel. Production deployments should separate governance, allocator, and emergency keys or contracts according to their operational risk.

Curator economics (fees)

There are two fee types. Both are minted as new SEP-41 shares to a configurable recipient.

FeeBasisCap
ManagementTime-weighted on AUM (rate × AUM × elapsed / 1yr), accrues regardless of performance5% / year
PerformanceAUM growth since the last accrual checkpoint; zero on flat or down periods50% of profit

Rates are WAD-scaled (1e18 = 100%). Each fee has its own recipient, and they can differ.

Operational details:

  • Checkpoint, not all-time high-water mark. Stellar share-pricing paths (DepositWithMin, RefreshFees, ResyncIdleBalance) first reconcile idle_assets against the live asset-token balance, then reset the fee_anchor to the reconciled total at the current ledger time. Profit is measured as current_AUM − anchor_AUM; if AUM is flat or down, the performance fee is zero. Because the anchor resets after each interaction, a recovery following a loss is chargeable — this is "growth since the last checkpoint", not "above the all-time peak". When fees are active, a deposit first crystallizes elapsed fees before the post-deposit anchor is written, so deposit principal cannot erase accrued fees.
  • Growth-rate cap (max_total_assets_growth_rate internally and --max-growth-rate-wad in the CLI, optional). Caps how fast AUM is allowed to count for fee accrual: effective_AUM = min(current, last × (1 + max_rate × dt/yr)). Relaxing or removing this cap is timelocked.
  • Refresh order matters. curator refresh-fees reconciles the live idle token balance, but it does not query every adapter. Refresh changed markets first, then crystallize fees against the resulting aggregate NAV.
tmplr-soroban-vault curator refresh-markets \
  --caller GALLOCATOR... \
  --markets 0,1
tmplr-soroban-vault curator refresh-fees

Set up the operator CLI

The examples below assume tmplr-soroban-vault is on PATH. From a source checkout, run the same commands with:

cargo run -p templar-soroban-vault-cli -- <arguments>

The current stack uses stellar-cli v26 and Rust 1.92. Run the repository's Stellar CLI installer or enter its devenv before operating a vault.

Create a public profile for repeatable network, RPC, manifest, and address defaults:

tmplr-soroban-vault profile init testnet
tmplr-soroban-vault --profile testnet doctor

Profiles must not contain seeds or secret keys. Keep signing material in the Stellar keystore, select it with stellar keys use <identity>, or provide an ephemeral secret through STELLAR_ACCOUNT. Never place a seed phrase or secret key in --source-account.

The deployment manifest defaults to:

contract/vault/soroban/.deploy-state/manifest.json

It records contract IDs, constructor arguments, artifact hashes, initialization state, and successful transaction audit records. Treat it as operational state, back it up, and pass --state explicitly when operating more than one vault.

Deploy a Stellar vault stack

Plan the deployment before writing to the network:

tmplr-soroban-vault deploy plan stack \
  --admin GCURATOR_OR_MULTISIG... \
  --asset-token CASSET... \
  --governance-timelock-ns 86400000000000 \
  --blend-pool CBLENDPOOL...

Then deploy the same configuration:

tmplr-soroban-vault deploy stack \
  --admin GCURATOR_OR_MULTISIG... \
  --asset-token CASSET... \
  --governance-timelock-ns 86400000000000 \
  --blend-pool CBLENDPOOL...

tmplr-soroban-vault status
tmplr-soroban-vault reconcile --json

deploy stack checkpoints the manifest after each upload, deployment, import, and initialization step. Reruns reuse recorded contract IDs and remotely available WASM. Use --force-new only when fresh contract instances are the explicit intent.

If deployment stops after one or more transactions:

tmplr-soroban-vault reconcile --json
tmplr-soroban-vault deploy repair --json
tmplr-soroban-vault deploy resume \
  --governance-timelock-ns 86400000000000 \
  --blend-pool CBLENDPOOL...

Resume only when reconciliation reports safe_to_resume: true. status reads the manifest; reconcile compares it with chain state and is the stronger check.

Mainnet writes require the global --allow-mainnet-write flag. A zero governance timelock additionally requires --allow-zero-timelock and should be limited to explicit local/test configurations.

Governance lifecycle

The deployment --admin becomes both the governance admin and initial vault curator. Governance can later assign a different curator, Sentinel, and allocator set.

A submission always returns a proposal ID. Directionally safe actions may be executed during submission; timelocked actions remain in the pending queue. Do not assume every returned ID needs a later accept call.

Inspect queued proposals before accepting them:

tmplr-soroban-vault governance queue
tmplr-soroban-vault governance explain --proposal-id 7
tmplr-soroban-vault governance accept \
  --admin GCURATOR_OR_MULTISIG... \
  --proposal-id 7

governance accept-ready is useful for routine automation, but exact proposal IDs are safer for high-impact changes. In particular, inspect and accept market cap proposals by ID; a textual cap filter can also match cap-group actions.

Exact timing rules

Governance timelocks are configured per action kind between 0 and 30 days. The initial value is chosen at deployment; there is no universal production default.

ChangeContract behavior
PauseOnly the Sentinel can pause, and it is immediate. Governance submit-set-paused --paused is rejected.
UnpauseGovernance proposal; timelocked under Pause.
RestrictionsSentinel may apply only a tightening change immediately. Every governance-admin restrictions submission is timelocked.
FeesA proposal containing only fee decreases and/or a tighter growth cap executes immediately if recipients do not change. Any fee increase, recipient change, or growth-cap relaxation/removal is timelocked.
Market capLowering an existing cap, including setting it to 0, executes immediately. A new market or cap increase is timelocked.
Cap groupsA new group cap, a cap increase, and every membership change are timelocked. Decreasing a known absolute or relative group cap executes immediately. Relative caps cannot exceed 100%.
Supply queue, allowed adapters, allocatorsTimelocked.
Curator, governance, admin, market removal, skim, upgrade, migrationTimelocked.
Sentinel appointmentThe first appointment may execute immediately; replacing an existing Sentinel is timelocked.
Timelock configurationIncreasing a duration executes immediately. Decreasing one is queued under the TimelockConfig timelock.
Withdrawal and idle-resync cooldownsEvery change is timelocked.

The governance admin can permanently disable an action kind with abdicate. Abdication is irreversible; confirm the exact action kind and recovery implications before submitting it.

Fees example

Fee values are WAD-scaled integers: 1e18 = 100%.

tmplr-soroban-vault governance submit-set-fees \
  --admin GCURATOR_OR_MULTISIG... \
  --performance-fee-wad 200000000000000000 \
  --performance-recipient GPERFORMANCE... \
  --management-fee-wad 20000000000000000 \
  --management-recipient GMANAGEMENT...

The command prints a semantic old/new diff and requires interactive confirmation or --yes. Inspect whether the proposal executed immediately or entered the queue before running accept.

Cap groups

Cap groups limit correlated routes together. When both limits are configured, the effective ceiling is:

min(absolute_cap, relative_cap × total_assets)

Absolute caps use raw asset base units and relative caps use WAD:

tmplr-soroban-vault governance submit-set-group-cap \
  --admin GCURATOR_OR_MULTISIG... \
  --group blue-chip \
  --cap 50000000000000

tmplr-soroban-vault governance submit-set-group-rel-cap \
  --admin GCURATOR_OR_MULTISIG... \
  --group blue-chip \
  --relative-cap 400000000000000000

tmplr-soroban-vault governance submit-set-group-member \
  --admin GCURATOR_OR_MULTISIG... \
  --market-id 0 \
  --group blue-chip

New group limits and all membership assignments are timelocked. Inspect and accept each proposal ID before relying on the group.

Sentinel emergency actions

Sentinel pause and restriction tightening are direct governance-contract entrypoints, not queued CLI governance proposals:

stellar contract invoke \
  --id "$SOROBAN_GOVERNANCE" \
  --source-account sentinel \
  -- set_paused \
  --caller GSENTINEL... \
  --paused true

stellar contract invoke \
  --id "$SOROBAN_GOVERNANCE" \
  --source-account sentinel \
  -- set_restrictions \
  --caller GSENTINEL... \
  --mode 1 \
  --accounts '["GACCOUNT..."]'

Restriction modes are 0 = none, 1 = blacklist, and 2 = whitelist. The governance contract rejects a Sentinel restriction change that relaxes the current policy.

Restoring normal operation uses governance and waits for the configured timelock:

tmplr-soroban-vault governance submit-set-paused \
  --admin GCURATOR_OR_MULTISIG...
tmplr-soroban-vault governance queue --kind pause

The CLI's boolean --paused flag defaults to false when omitted. Supplying the flag requests true, which this governance submission path rejects.

Pausing also blocks ordinary allocation and refresh operations. If an incident requires liquidity recall, decide whether to lower market caps and unwind routes before a global pause. Allocator-emergency recovery such as abort-withdrawing remains available while paused.

Add and activate market routes

Deploying an adapter does not make it usable by the vault. An active market requires three accepted governance states:

  1. The adapter contract is in the allowed-adapter set.
  2. The market ID has a nonzero cap in raw asset base units.
  3. The supply queue binds that market ID to the adapter address.

Add adapters to an existing or imported stack:

tmplr-soroban-vault deploy adapters \
  --vault CVAULT... \
  --governance CGOVERNANCE... \
  --asset-token CASSET... \
  --blend-pool CBLENDPOOL... \
  --custodian GCUSTODIAN...

Then submit the policy in order:

tmplr-soroban-vault governance submit-set-allowed-adapters \
  --admin GCURATOR_OR_MULTISIG... \
  --adapters CBLENDADAPTER...,CCUSTODIALADAPTER...
# After the allowed-adapters proposal is ready:
tmplr-soroban-vault governance accept-ready \
  --admin GCURATOR_OR_MULTISIG... \
  --kind allowed-adapters

tmplr-soroban-vault governance submit-set-cap \
  --admin GCURATOR_OR_MULTISIG... \
  --market-id 0 \
  --cap 1000000000
# After the cap proposal is ready, verify and accept its exact ID:
tmplr-soroban-vault governance explain \
  --proposal-id CAP_MARKET_0_PROPOSAL_ID
tmplr-soroban-vault governance accept \
  --admin GCURATOR_OR_MULTISIG... \
  --proposal-id CAP_MARKET_0_PROPOSAL_ID

tmplr-soroban-vault governance submit-set-supply-queue \
  --admin GCURATOR_OR_MULTISIG... \
  --entry 0:CBLENDADAPTER... \
  --entry 1:CCUSTODIALADAPTER...
# After the supply-queue proposal is ready:
tmplr-soroban-vault governance accept-ready \
  --admin GCURATOR_OR_MULTISIG... \
  --kind supply-queue

Each --entry is market_id:adapter_address. Market IDs are stable identities, not queue positions:

  • Reordering the queue does not remap a market to another adapter.
  • An existing market ID cannot be rebound to a different adapter.
  • Supply requires the bound adapter to remain allowed.
  • Withdrawal keeps using the stored binding, so liquidity can be recovered after an adapter is removed from new supply.
  • Queue entries must be unique, enabled markets with nonzero caps. Practical queue size is also bounded by Soroban transaction resource limits.

Adapter trust boundaries

  • Blend adapter — queries and operates a configured Blend pool on Stellar.
  • Custodial adapter — forwards assets to a configured custodian or multisig. The off-chain route, custody controls, NAV reporting, and liquidity-return procedure are part of the vault's trust boundary.

A custodial withdrawal only releases assets already returned to the adapter on Stellar; it does not initiate or prove an external unwind. Reported NAV updates must match both the current stored amount and the exact next nonce:

stellar contract invoke \
  --id "$CUSTODIAL_ADAPTER_ID" \
  --source-account custodian \
  -- set_reported_assets \
  --caller GCUSTODIAN... \
  --asset CASSET... \
  --expected_current 800000000 \
  --amount 1000000000 \
  --report_nonce 42

Before using a custodial route, document signer recovery, NAV cadence, report approval, reconciliation, and delayed-liquidity procedures.

Day-to-day allocation and accounting

Routine allocation commands use a positive amount and a stable market ID. The allocator does not choose an adapter at execution time.

tmplr-soroban-vault curator refresh-markets \
  --caller GALLOCATOR... \
  --markets 0,1

tmplr-soroban-vault curator allocate-supply \
  --caller GALLOCATOR... \
  --market 0 \
  --amount 100 \
  --asset-decimals 7

tmplr-soroban-vault curator allocate-withdraw \
  --caller GALLOCATOR... \
  --market 0 \
  --amount 25 \
  --asset-decimals 7

Decimal flags are converted without floating point. Automation can use --amount-raw, --assets-raw, or --shares-raw for exact base units.

The accounting behavior differs by direction:

  • allocate-supply transfers assets to the bound adapter, calls its supply method, reads total_assets(asset), and stores the observed route NAV.
  • allocate-withdraw requests an amount from the adapter, verifies the actual vault token-balance delta matches the adapter's return value, and subtracts the realized amount. It does not refresh the adapter's remaining NAV.
  • refresh-markets reads total_assets(asset) for the selected routes and replaces their stored principals. Run it after yield, loss, or a custodial NAV report and before fee/share-rate decisions.

Two maintenance calls are permissionless even though they are grouped under curator in the CLI:

tmplr-soroban-vault curator resync-idle
tmplr-soroban-vault curator refresh-fees

resync-idle requires the vault to be idle and is rate-limited by the idle resync cooldown, which defaults to 120 seconds. refresh-fees reconciles the live idle balance and advances the fee checkpoint. Neither call substitutes for refresh-markets when adapter NAV has changed.

Withdrawal operations

The Stellar vault has two distinct exit paths.

Atomic idle-liquidity exit

user atomic-withdraw and user atomic-redeem use the ERC-4626 proxy's slippage-protected atomic exit methods when the deployment manifest contains proxy_4626. The CLI first verifies that the recorded proxy interface exposes both atomic entrypoints; legacy proxies must be replaced rather than bypassed. Proxy-less imported deployments fall back to the vault's equivalent atomic commands. Both routes complete in one transaction only when the vault has enough idle assets. They never pull liquidity from an adapter. As a result, maxWithdraw and maxRedeem can be zero while the user's shares still represent assets deployed to markets.

tmplr-soroban-vault user preview --owner GUSER...

tmplr-soroban-vault user atomic-withdraw \
  --operator GUSER... \
  --assets 25 \
  --asset-decimals 7 \
  --max-shares-burned 25 \
  --share-decimals manifest

Queued withdrawal

Use the queued path when idle liquidity is insufficient:

The proxy-facing user withdraw and user redeem commands preserve its asynchronous ERC-7540-style compatibility methods. Use request-withdraw for the lower-level vault request surface with explicit share and minimum-asset inputs.

tmplr-soroban-vault user request-withdraw \
  --owner GUSER... \
  --shares 10 \
  --share-decimals manifest \
  --min-assets-out 9.9 \
  --asset-decimals 7

# After cooldown, recall enough market liquidity if needed.
tmplr-soroban-vault curator allocate-withdraw \
  --caller GALLOCATOR... \
  --market 0 \
  --amount 10 \
  --asset-decimals 7

# The operator is an authorized allocator/curator, not the withdrawing user.
tmplr-soroban-vault user execute-withdraw \
  --operator GALLOCATOR...

The queued path has these mechanics:

  • request-withdraw escrows shares and records a fixed asset claim at request time. The default cooldown is one hour.
  • execute-withdraw services the queue head; it does not select a request ID.
  • The caller must have allocator authority. The command is under user because it completes a user flow, not because any user may execute it.
  • The head request must be cooled down and fully covered by idle assets. There is no partial payout.
  • Finishing an allocation does not automatically progress the withdrawal queue; call execute-withdraw separately.
  • The queue does not reserve idle assets against later atomic exits. Curators must monitor queued claims and maintain enough idle liquidity.
  • There is no user cancellation path. Monitor request and payout events by request ID and alert on stalled heads.

If execution is already stuck in Withdrawing, an allocator, Sentinel, or curator may abort the exact active operation:

tmplr-soroban-vault curator abort-withdrawing \
  --caller GALLOCATOR_OR_SENTINEL... \
  --op-id 42

This is an incident-recovery action, not a normal withdrawal tool. A successful abort validates the active operation ID, restores collected idle accounting, refunds escrowed shares, removes the affected queue head, emits a WithdrawalStopped event, and returns the vault to Idle.

TTL and archival operations

Soroban contract data is not permanent. Every vault needs an automated TTL job; ordinary transaction traffic is not a substitute for a keeper schedule.

tmplr-soroban-vault extend-ttl

The CLI attempts the vault runtime, governance, ERC-4626 proxy, curator proxy, share token, and every adapter recorded in the manifest. The asset token has no deployment-wide TTL entrypoint and is reported as skipped. Treat a failed or unexpectedly skipped component as an operational alert.

Vault, governance, proxy, and custodial-adapter maintenance uses permissionless contract entrypoints. Share-token and Blend-adapter TTL entrypoints are admin-gated, so the aggregate command does not invoke them. It instead uses Stellar protocol-level operations to extend each contract instance and its WASM code. The configured source account signs and pays for those operations; no vault or governance contract authorization is required. The legacy --caller option remains accepted for backward compatibility but is ignored.

Each contract owns its own TTL. Extending the vault runtime does not extend governance, proxies, share-token holder entries, adapter storage, or oracle storage. Run the aggregate command before archival: an extend operation cannot revive an archived entry. If a contract is already archived, restore both its instance with stellar contract restore --id ... and its WASM code with stellar contract restore --wasm-hash ... before rerunning extend-ttl. Contract-specific persistent entries may require separate restore or renewal operations.

Safety and automation

  • Use --dry-run to print redacted Stellar commands and manifest decisions without writes.
  • Every contract write is simulated before submission. Review auth, footprint, resource, fee, and contract-error output on stderr.
  • Use --json for stable machine-readable responses or --json-lines for long-running automation. The schema lives at tools/soroban-vault-cli/schema/output.schema.json.
  • Mainnet writes require --allow-mainnet-write.
  • Dangerous governance submissions print an old/new semantic diff and require --yes or interactive confirmation.
  • Successful writes append transaction metadata to the manifest. Preserve that audit trail alongside external monitoring and event indexing.
  • Run reconcile after interrupted deployment, unexpected RPC results, or any manual contract operation that may have diverged from the manifest.
  • Generate operator completions or a manpage with completions and man rather than copying stale command snippets into private runbooks.

Before a policy or allocation change, verify the manifest/network, refresh any changed adapter NAV, inspect the current proposal queue, and identify the exact role that must authorize the action. Afterward, verify the transaction result, runtime state, adapter accounting, relevant events, and the manifest audit record.

Monitoring and Risk Management

Templar Protocol uses available tools and established practices for monitoring protocol health and managing risks.

Protocol Monitoring Tools

Available Monitoring

Bot Infrastructure

The protocol includes operational bots for automated tasks:

  • Liquidation Bot: Monitors positions and executes liquidations

    • Configurable intervals and concurrency
    • Market registry monitoring
    • Oracle price feed integration
    • Automated liquidation execution
  • Accumulator Bot: Handles interest accumulation

    • Periodic interest calculations
    • Multi-market support
    • Configurable execution parameters

Gas Usage Monitoring

Gas analysis tools provide performance insights:

./script/gas-report.sh

This generates detailed reports on:

  • Function execution costs
  • Snapshot iteration limits
  • Performance bottlenecks

Manual Monitoring Procedures

Protocol Health Checks

Regular checks can be performed using:

  1. Market Status: Query market configurations and states

    # Get market configuration
    near contract call-function as-read-only <market-address> get_configuration json-args {} network-config mainnet now
    
    # Check current market snapshot
    near contract call-function as-read-only <market-address> get_current_snapshot json-args {} network-config mainnet now
    
    # Get borrow asset metrics
    near contract call-function as-read-only <market-address> get_borrow_asset_metrics json-args {} network-config mainnet now
    
    # List all deployed markets from registry
    near contract call-function as-read-only v1.tmplr.near list_deployments json-args '{"offset": 0, "count": 100}' network-config mainnet now
    
  2. Oracle Health: Verify price feed freshness and accuracy

    # Check oracle prices
    near contract call-function as-read-only pyth-oracle.near get_price json-args '{"price_identifier": "<asset-price-id>"}' network-config mainnet now
    
    # Check LST oracle adapter
    near contract call-function as-read-only lst.oracle.tmplr.near get_price_data json-args '{}' network-config mainnet now
    

    Price Feed Status: Monitor price feed health at Pyth Network Price Feeds

Market Data Analysis

Using available view functions:

  • Supply Positions: Monitor individual and aggregate supply positions

    # Get supply positions
    near contract call-function as-read-only <market-address> list_supply_positions json-args '{"offset": 0, "count": 100}' network-config mainnet now
    
  • Withdrawal Queue: Check pending withdrawal requests

    # Check withdrawal queue status
    near contract call-function as-read-only <market-address> get_supply_withdrawal_queue_status json-args {} network-config mainnet now
    
  • Historical Snapshots: Analyze market history

    # Get finalized snapshots for historical analysis
    near contract call-function as-read-only <market-address> list_finalized_snapshots json-args '{"offset": 0, "count": 10}' network-config mainnet now
    
  • Total Value Locked: Monitor TVL at DefiLlama - Templar Protocol

  • Utilization Rate: Calculate from borrow asset metrics

    # Get borrow asset metrics to calculate utilization (borrowed / available)
    near contract call-function as-read-only <market-address> get_borrow_asset_metrics json-args {} network-config mainnet now
    
  • Current Interest Rate: Monitor current rate for supply positions

    # Get current yield rate for suppliers
    near contract call-function as-read-only <market-address> get_last_yield_rate json-args {} network-config mainnet now
    

    Note: Historical interest rate analysis requires an indexer for time-series data

Risk Management

Economic Risk Assessment

Available Analysis Tools

  • Market Configuration Review: Analyze MCR ratios and interest rate models TODO: Get parameters from each market deployed
  • Oracle Price Monitoring: Track price volatility and feed reliability
  • Liquidation Efficiency: Monitor liquidation success rates TODO: Get data from liquidator
  • Position Analysis: Assess individual and aggregate position health
    • Individual positions: My Account
    • Aggregate analysis: TODO: Create from contract data

Risk Mitigation Strategies

  • Conservative Parameters: Well-tested collateralization ratios
  • Oracle Integration: Multiple validation layers for price feeds
  • Liquidation Incentives: Economic incentives for timely liquidations
  • Interest Rate Models: Dynamic models responding to market conditions

Operational Monitoring

For operational monitoring procedures, refer to:

Criminal Activity Monitoring

SEVERE Account Labels

  • SEVERE Account Detection: Telegram notification of SEVERE accounts interacting with Templar contracts
  • Monitoring Repository: Details available at templar-monitoring

Frontend Security

Templar Protocol is committed to maintaining robust security practices across our frontend infrastructure to protect users and their assets. The application hosted at app.templarfi.org implements multiple layers of defense to ensure the integrity, availability, and trustworthiness of the interface through which users interact with Templar's smart contracts.

Hosting & DDoS Protection

The Templar frontend is deployed on Vercel's edge network, which provides built-in distributed denial-of-service (DDoS) mitigation at the infrastructure level. Vercel's global CDN automatically absorbs and filters volumetric attacks, rate-limits abusive traffic, and ensures high availability across geographically distributed edge nodes. This architecture provides resilience against Layer 3, Layer 4, and Layer 7 attack vectors without requiring additional proxy configurations.

DNS Security

The templarfi.org domain is managed with the following protections in place:

  • Registrar lock is enabled to prevent unauthorized domain transfers or modifications.
  • DNSSEC validation is supported through our DNS provider to ensure the authenticity of DNS responses and prevent cache poisoning attacks.
  • DNS records are configured to resolve exclusively to Vercel's verified edge infrastructure, minimizing the risk of DNS hijacking or man-in-the-middle redirection.
  • Access to DNS management is restricted to authorized personnel with multi-factor authentication (MFA) enforced on all accounts with domain-level permissions.

Frontend Integrity & Modification Detection

Templar employs several practices to detect and prevent unauthorized modifications to the frontend application:

  • Immutable deployments: Each deployment on Vercel produces an immutable, content-addressed build artifact. Previous deployments can be instantly promoted or rolled back, ensuring that any unauthorized change can be quickly identified and reversed.
  • Build verification: The frontend is built from a version-controlled source repository with branch protection rules. All production deployments originate from reviewed and approved code changes.
  • Subresource Integrity (SRI): Where applicable, external resources loaded by the frontend use integrity hashes to ensure that third-party scripts and stylesheets have not been tampered with.
  • Content Security Policy (CSP): HTTP security headers are configured to restrict the sources from which scripts, styles, and other resources can be loaded, mitigating cross-site scripting (XSS) and code injection risks.

Intrusion Detection & Monitoring

The Templar frontend leverages monitoring and alerting mechanisms to detect suspicious activity:

  • Vercel's built-in analytics and logging provide visibility into traffic patterns, error rates, and deployment activity, enabling rapid detection of anomalous behavior.
  • Automated alerts are configured for deployment failures, unusual traffic spikes, and error rate thresholds.
  • Access control: Administrative access to the deployment platform and associated infrastructure accounts is restricted by role-based permissions and protected by multi-factor authentication.

Client-Side Security Best Practices

The frontend application follows industry-standard security practices:

  • No private key handling: The frontend never requests, stores, or transmits private keys. All transaction signing is delegated to the user's connected wallet.
  • Strict input validation: All user inputs are validated and sanitized on the client side before any contract interaction to prevent injection attacks and malformed transaction data.
  • HTTPS enforced: All connections to app.templarfi.org are served exclusively over TLS, with HTTP Strict Transport Security (HSTS) headers enforced to prevent protocol downgrade attacks.
  • Minimal third-party dependencies: The frontend minimizes its dependency surface area, and all packages are reviewed and version-pinned to reduce supply chain risk.
  • Wallet interaction safety: Transaction parameters are constructed transparently, enabling users to verify contract calls and parameters in their wallet interface before signing.

Incident Response

In the event that a frontend compromise is detected or suspected, Templar's response protocol includes:

  • Immediate rollback to the last known-good immutable deployment.
  • Revocation and rotation of any compromised credentials or API keys.
  • Communication to users via official channels (Twitter, Discord, and documentation site) advising them to verify the application URL and refrain from signing transactions until the all-clear is issued.
  • Post-incident review and publication of findings where appropriate.

For questions or to report a security concern related to the Templar frontend, see Security Reporting.

Testing & Code Coverage

Test Execution

Run the complete local suite through the same entrypoints used by CI:

just test

Use just test-fast for the complete non-node gate, including non-node integration targets, or just test-sandbox for the node-backed gate. The sandbox recipe prebuilds NEAR contracts before starting its pooled neard instances; pass --stale to reuse the contracts already built into target/near.

Run the artifact drift check separately when validating the release catalog — pure in-memory invariant checks, no builds:

./script/check-artifact-drift.sh

Local Testing

Running Tests with Coverage

# Generate and open HTML coverage for the fast library-test cut
just coverage

# Generate coverage.lcov
just coverage-lcov

Test Categories

  • Unit tests: Module-level functionality
  • Integration tests: Cross-module interactions
  • Contract tests: Smart contract behavior
  • End-to-end tests: Full workflow validation

Performance Testing

Gas Usage Analysis

Gas usage analysis is available through existing tools:

./script/gas-report.sh

This generates a gas report for market operations, including average gas costs for individual operations and snapshot iteration limits.

Test-Gate Timing

The node gate is the slowest thing we run, so its cost is measured rather than guessed. Two tools:

# Time the harness primitives on a dedicated neard: block-latency floor,
# per-transaction and per-patch costs, fixture setup.
just bench-sandbox

# Compare two whole-suite runs. `just test-sandbox` writes per-test timings to
# target/nextest/sandbox/junit.xml (see [profile.sandbox.junit] in
# .config/nextest.toml); copy it aside before and after a change.
./script/bench/junit-diff.py before.xml after.xml

junit-diff.py totals are summed per-test durations. The gate runs several tests concurrently, so those intervals overlap: the totals measure work done, not gate wall clock. Its per-test breakdown is the point — a change that halves the suite can still make one test much worse.

What the measurements established, so it need not be re-derived:

  • Node round-trips dominate; WASM payload is ~free. 570KB of extra contract code adds ~18ms to a deploy. Installing contract code via sandbox_patch_state works but is not faster, and it forfeits batching deploy+init into one transaction. Same verdict rules out global contracts. Don't retry either.
  • sandbox_patch_state costs ~200ms per call, not per record. Minting N accounts in one patch therefore costs roughly what minting one does — hence the harness's batched create_accounts.
  • Block production delay is the other lever, and it is local-only. See the sandbox cadence note below.

Sandbox Block Cadence

Locally the harness runs neard at a 40ms min_block_production_delay instead of the stock 120ms, which roughly halves the node gate. CI pins the stock 120ms (NEAR_SANDBOX_BLOCK_MS in .github/workflows/test.yml): a 4-vCPU runner cannot sustain four nodes producing blocks 3× as often, and attempting it caused widespread transaction-finality failures. Reducing parallelism to compensate was measured and is slower than stock.

Override the cadence with NEAR_SANDBOX_BLOCK_MS=<ms>. max_block_production_delay is compensated in the opposite direction so that avg(min, max) stays fixed at 310ms — nearcore credits a sandbox_fast_forward as delta_height × avg(min, max), so holding that average keeps every time-sensitive test's simulated time unchanged whatever the real cadence.

Because local blocks are faster than CI's, a test that leans on incidental block cadence to cross a time boundary will pass in one place and fail in the other. Advance chain time explicitly with fast_forward rather than relying on how long some operations happen to take.

Glossary

This glossary provides definitions for key terms used throughout the Templar Protocol documentation and smart contracts.

A

APY (Annual Percentage Yield): The total return on an investment over one year. In Templar, this represents the effective yearly return for suppliers or the cost for borrowers.

Asset Pair: The combination of collateral asset and borrow asset that defines a market (e.g., BTC/USDC means Bitcoin collateral, USDC borrowing).

B

Borrow Asset: The token that users can borrow from the market. Typically a stablecoin like USDC, but can be any supported token.

Borrow Position: A user's borrowing account containing collateral deposits, borrowed amounts, accumulated interest, and current status.

Borrower: A user who deposits collateral and borrows assets from the market, paying interest on the borrowed amount.

C

Collateral Asset: The token deposited by borrowers to secure their loans. Must be worth more than the borrowed amount due to over-collateralization requirements.

Collateralization Ratio (CR): The ratio of collateral value to borrowed value. A 150% ratio means $150 of collateral backs $100 of debt.

Compounding: The process of reinvesting earned yield to generate additional returns over time.

D

Debt: The total amount owed by a borrower, including principal plus accumulated interest and fees.

E

EMA: Exponentially-weighted moving average. A time-series smoothing technique that favors recency.

F

FMV (Fair Market Value): The current market price of an asset as determined by oracle price feeds.

G

Gas: The computational cost for executing transactions on the NEAR blockchain.

H

Harvest Yield: The action of claiming accumulated yield from a supply position, which can then be withdrawn.

I

Interest Accumulation: The process of calculating and adding accrued interest to a borrower's total liability.

L

Lending: The general practice of providing assets to borrowers in exchange for interest payments. In Templar, suppliers lend to the market pool.

Liability: The total debt owed by a borrower, including principal, accumulated interest, and fees.

Liquidation: The forced sale of a borrower's collateral when their position becomes undercollateralized or expires.

Liquidator: A third party who performs liquidations by repaying part of a borrower's debt in exchange for discounted collateral.

Liquidator Spread: The discount liquidators receive when purchasing collateral, serving as incentive for providing the liquidation service.

Liquidity: The availability of assets in the market for borrowing or withdrawal. When liquidity is low, withdrawal requests may need to wait in the queue.

Liquidity Pool: The combined supply of assets deposited by all suppliers in a market, available for borrowers to access.

M

Market: A smart contract managing lending and borrowing for a specific asset pair (e.g., BTC/USDC market).

Maximum Usage Ratio: The maximum percentage of supplied assets that can be borrowed from a market, preventing over-utilization and maintaining liquidity reserves.

MCR (Minimum Collateralization Ratio): The minimum ratio of collateral value to borrowed value required to maintain a position. Different MCR levels trigger maintenance requirements or liquidation.

MCR Liquidation: The minimum collateralization ratio below which a position becomes eligible for liquidation.

MCR Maintenance: The minimum collateralization ratio required for new borrows or collateral withdrawals.

N

NEAR Intents: A NEAR Protocol feature that allows users to express desired outcomes (intents) that can be fulfilled by solvers, enabling more flexible and efficient transaction execution. See the official NEAR Intents documentation.

NEP-141: The NEAR Protocol standard for fungible tokens, similar to Ethereum's ERC-20. See the official NEP-141 specification.

NEP-245: The NEAR Protocol standard for multi-token contracts, similar to Ethereum's ERC-1155. See the official NEP-245 specification.

O

Oracle: A service providing real-time price data for assets, essential for calculating collateralization ratios and liquidations.

Origination Fee: A fee charged when creating a new borrow position, can be flat amount or percentage-based.

Over-collateralization: The requirement for borrowers to deposit collateral worth more than the borrowed amount, providing a safety buffer against price volatility.

P

Partial Liquidation: Liquidating only enough collateral to bring a position back to the maintenance MCR, rather than liquidating the entire position.

Principal: The original amount borrowed or supplied, excluding accumulated interest and fees.

Protocol Revenue: Fees collected by the protocol from borrowers and suppliers, distributed to suppliers and other accounts according to configured yield weights.

Pyth Network: A decentralized oracle network providing high-frequency price feeds for various assets.

R

Registry: A smart contract that manages deployment and versioning of market contracts within the Templar Protocol.

Repay: The action of returning borrowed assets plus interest to reduce or eliminate a borrower's debt.

S

Snapshot: A point-in-time record of market state including interest rates, asset amounts, and yield distribution.

Stablecoin: A cryptocurrency designed to maintain stable value, typically pegged to a fiat currency like USD. Commonly used as borrow assets in lending protocols.

Static Yield: A fixed allocation of market revenue to specific accounts, independent of their supply activity. Defined in the market's yield_weights configuration, static yield is distributed proportionally to designated accounts and can be withdrawn using the withdraw_static_yield function.

Supply: The total amount of assets deposited by suppliers that are available for borrowing in a market.

Supplier: A user who deposits assets into the market to earn yield from borrower interest payments.

Supply Withdrawal Fee: A fee charged when suppliers withdraw their assets from the market, configured per market to manage liquidity.

T

Time Chunk: A configurable time period (based on blocks, epochs, or timestamps) that determines when new snapshots are created.

Transfer Call: A token transfer that includes data, allowing the receiving contract to execute logic based on the transfer.

U

Undercollateralized: A borrow position where the collateral value falls below the required minimum ratio, making it eligible for liquidation.

Utilization Rate: The percentage of supplied assets currently borrowed. Calculated as: borrowed_amount / total_supplied_amount.

W

Withdrawal Queue: A first-in-first-out system for processing supply withdrawals when market liquidity is insufficient.

Y

Yield: The return earned by suppliers on their deposited assets, generated from borrower interest payments and fees.

Security Reporting

Templar Protocol takes security seriously and encourages responsible disclosure of security vulnerabilities.

All smart contracts are open-source and use reproducible builds for maximum transparency.

Bug Bounty

Templar has partnered with Immunefi to reward up to $100k for in-scope smart contract vulnerabilities.

To report smart contract vulnerabilities, please participate in our Immunefi bug bounty.

Security Contact

For security vulnerabilities and sensitive issues, please email security@templarprotocol.com.

Security Alerts

Important security notices will be posted on the official Discord server, Telegram channel, and X (Twitter) account.

Audit Information

Audit reports are available on GitHub.

Responsible Disclosure

If you have discovered a security issue, please follow these steps:

  1. Report: Send vulnerability details to Immunefi bug bounty for smart contract vulnerabilities or security@templarprotocol.com for other security issues.
  2. Investigation: Security team will assess the report.
  3. Resolution: Fix development and deployment.
  4. Public Disclosure: Coordinated disclosure after fix.

Security reports should include:

  • A clear description of the vulnerability.
  • Steps to reproduce the issue.

Notes

Sending funds to the market contract

When sending funds to a contract, you must call the asset contract's *_transfer_call function with the market as the receiver_id. So, for a token contract that implements the NEP-141 (Fungible Token) standard, you must call ft_transfer_call, specifying the market account ID as the receiver_id argument. For a token contract that implements the NEP-245 (Multi Token) standard, you must call mt_transfer_call.

If the funds are not sent using a *_transfer_call function, the contract will not be able to respond to the transfer: the funds will not be tracked by the contract, they will not be added to the supply, and the funds cannot be returned or withdrawn.

Contract interaction syntax

Contract interactions will be shown using near-cli-rs syntax. It can be installed via:

cargo install near-cli-rs

Example

near contract call-function as-transaction \
    ibtc-usdc.v1.tmplr.near borrow \
    json-args '{ "amount": "1000" }' \
    prepaid-gas '100.0 Tgas' \
    attached-deposit '0 NEAR' \
    sign-as account.near \
    network-config mainnet \
    sign-with-keychain \
    send

This command calls the function borrow on the contract ibtc-usdc.v1.tmplr.near with the arguments payload:

{
  "amount": "1000"
}

Large numbers are serialized as strings instead of numerical literals to ensure that the precision limitations of JSON parsers do not affect the values. (See "Notes on Serialization" on docs.near.org.)

The command attaches 100 teragas units and 0 NEAR to the call, signs the transaction as account.near using a key saved to the local keychain, and sends the transaction to NEAR mainnet.

Please refer to the near-cli-rs user guide for more details.