Bench Overview

Myth Bench enables permissionless constant price markets on Algorand: sell any amount of an asset at a fixed price, straight from an on-chain escrow.

Myth Bench is available at myth.finance/bench

A bench is a dedicated smart contract that holds the tokens you want to sell and hands them out to buyers at the price you set. There is no order book, no liquidity pool and no slippage: every buyer pays the same price, until the bench sells out or ends.

In a nutshell:

  1. A seller creates a bench, choosing the asset to sell, the purchase asset (ALGO or any ASA), the amount and the price.
  2. The Bench registry deploys a dedicated escrow contract for the sale and moves the tokens into it.
  3. Buyers send the purchase asset to the bench and instantly receive tokens at the fixed rate, atomically.
  4. The seller withdraws proceeds at any time, and closes the bench once it is over.

Features

  • Sell ALGO or any ASA, priced in ALGO or any ASA
  • One fixed price for the whole sale, no slippage
  • Schedule a start time, an end time, or both, or leave the bench open until it sells out
  • Per-account purchase limits, enforced on-chain
  • Optional Terms & Conditions link and restricted countries list (see Metadata)
  • Designate a beneficiary: the account that receives the proceeds and manages the bench can differ from the account creating it
  • Withdraw proceeds at any time, while the sale is still running
  • Automatic end when the bench sells out
  • Immutable, non-upgradable escrow contract per bench (see Immutability)
  • Directory of all benches on-chain, and a TypeScript SDK for programmatic access

Who can create a bench?

Anyone. Creation is permissionless for assets that pass the asset eligibility checks. On mainnet this currently means ALGO and any Pera Verified ASA (plus assets explicitly allowlisted by Myth Finance).

Fees

  • A one-off setup fee in ALGO when creating a bench (currently 20 ALGO on mainnet)
  • A platform fee on each sale, deducted from the seller's proceeds in the purchase asset (currently 0.15%)

Buyers pay no platform fees. See Fees for details.

Roadmap

  • Private sales and OTC support via buyer whitelist
  • Enforce minimum purchase amount
  • Detailed accounting export

Read more about how Bench works or navigate to a specific section using the menu.

How it Works

Bench is made up of two kinds of smart contracts:

  • The Bench Registry: a single contract per network. It creates benches, keeps an on-chain directory of all of them, and holds platform-wide settings such as fees and asset eligibility rules.
  • Bench instances: one dedicated escrow contract per sale. It holds the tokens for sale, accepts payments, delivers tokens to buyers, and tracks proceeds and fees.

Lifecycle of a bench

1. Creation

The seller (the creator) submits a transaction group to the registry containing the setup fee and escrow seed in ALGO, and the tokens to sell. The registry deploys a fresh bench contract, opts it into the relevant assets, moves the tokens in and configures it: owner, price, amount, schedule and purchase limit. See Creating a Bench.

The new bench is recorded in the registry's directory so that the frontend and the SDK can discover it.

2. Sale

Buyers send the purchase asset (ALGO or the configured ASA) to the bench together with a buy call, and receive tokens at the fixed price within the same atomic transaction group. See Buying.

  • If a start time was set, purchases before it are rejected.
  • If no start time was set, the bench opens immediately, and the time of the first sale is recorded as its start time.
  • Each sale accrues the platform fee inside the bench, in the purchase asset.

The owner may withdraw proceeds at any point during the sale.

3. End

A bench ends when any of the following happens:

  • It sells out. As soon as the remaining amount is too small to be bought (see dust), the bench ends automatically.
  • Its end time (if one was set) passes.
  • The owner ends it manually.

Once ended, no further purchases are possible.

4. Close

After a bench has ended, the owner closes it. Closing pays out accrued platform fees, returns unsold tokens and all remaining proceeds to the owner, opts the contract out of the assets and refunds the ALGO escrow seed to the owner. The empty contract stays on-chain and is removed from the registry directory.

Roles at a glance

RoleWhoWhat
CreatorAny accountPays for the creation and supplies the tokens
Owner (beneficiary)Chosen by the creator, defaults to the creatorReceives proceeds, manages the bench and its metadata
BuyerAny accountBuys tokens
Fee adminMyth FinanceWithdraws platform fees, can lower a bench's fee, can perform management actions on behalf of the owner
Registry adminMyth FinancePlatform-wide settings

See Roles for the complete list of privileged actions.

Buying

Buying from a bench is a single transaction group: a payment to the bench, followed by a buy application call. The bench delivers the tokens within the same group, so purchases are atomic: either you receive the tokens at the fixed price, or nothing happens.

Benches can be browsed and bought from at myth.finance/bench, or programmatically with the SDK.

Requirements

  • If the bench sells an ASA, you must be opted in to it. The frontend and the SDK add the opt-in to the group automatically when needed.
  • You pay with an asset transfer if the bench is priced in an ASA, or with a payment if it is priced in ALGO.
  • The bench must be open: past its start time (if any) and before its end time (if any).
  • Your purchase may not exceed the remaining amount. Benches do not partially fill: a purchase for more than what is left is rejected.

What you receive

You choose how much of the purchase asset to spend; the bench sends back

tokens = spend × 100,000,000 / purchase_price_per_100m

rounded down to the smallest unit of the token being sold (see Pricing). Purchases that would round down to zero tokens are rejected.

Buyers pay no platform fee: the platform fee is deducted from the seller's proceeds. Buyers pay their own network transaction fees, including the fee of the inner transaction that delivers the tokens (0.001 ALGO extra on the buy call).

Purchase limits

A bench may enforce a maximum spend per account, denominated in the purchase asset. The limit is cumulative over the whole sale: once an account has spent up to the limit, further purchases from that account are rejected.

To keep track of each buyer's spend, the bench stores a small box (40 bytes) per buyer account. On your first purchase from such a bench, your transaction group includes a one-off payment of 0.0185 ALGO to the bench, covering the box's minimum balance requirement. This amount is not refunded to buyers; it is released to the bench owner when the bench is closed.

The frontend shows your remaining allowance on the bench page; the SDK exposes it as userMaxSpend.

Terms & Conditions and restricted countries

Sellers can attach a Terms & Conditions link and a list of restricted countries to a bench (see Metadata). The myth.finance frontend displays the Terms & Conditions link prominently, and hides the bench from visitors located in restricted countries.

Country restrictions are enforced by the frontend only, based on browser-side IP geolocation. They are not enforced by the smart contract.

Transaction group

#TransactionNotes
1Asset opt-inOnly if you are not yet opted in to the token being sold
2Payment of 0.0185 ALGO to the benchOnly for benches with a purchase limit, on your first purchase
3Payment or asset transfer to the benchThe amount you spend
4buy() application callExtra fee of 0.001 ALGO to cover the inner token transfer

The buy call must be immediately preceded by the payment (3), which in turn must be immediately preceded by the box payment (2) when one is required.

Creating a Bench

Benches are created through the Bench Registry, either from the myth.finance/bench frontend ("Create Bench") or with the TypeScript SDK.

Parameters

ParameterDescription
Asset for saleALGO or an ASA you hold. Must pass the eligibility checks.
Purchase assetALGO or the ASA buyers will pay with. Must pass the eligibility checks, and differ from the asset for sale.
Amount for saleTransferred into the bench escrow at creation.
PricePurchase asset per unit of the asset for sale. Stored on-chain as purchase_price_per_100m (see Pricing).
Start time (optional)Purchases are rejected before it. Leave empty to open immediately.
End time (optional)Purchases are rejected after it. Leave empty for no end time: the bench stays open until it sells out or is ended by the owner. Must be in the future, and after the start time.
Purchase limit per account (optional)Maximum cumulative spend per buyer account, in the purchase asset. See Buying.
Beneficiary (optional)The bench owner: receives all proceeds and manages the bench. Defaults to the creator.
Terms & Conditions URL (optional)Shown to buyers. See Metadata.
Restricted countries (optional)Enforced by the frontend only. See Metadata.

Setting a beneficiary other than yourself is irreversible. The creator keeps no control over the bench and cannot access its proceeds. Only the beneficiary (owner) and the Myth Finance fee admin can manage the bench, and proceeds only ever go to the owner.

Except for the metadata (Terms & Conditions, restricted countries), all parameters are immutable once the bench is created. Timing can only be shortened, by ending the bench early.

Costs

Creating a bench costs, in ALGO:

ItemAmountNotes
Setup feeRegistry setting setup_fee, currently 20 ALGO on mainnetPlatform fee, non-refundable
Escrow seed0.2 ALGO if one side is ALGO, 0.3 ALGO for two ASAsMinimum balance of the bench account and its asset opt-ins. Refunded to the owner when the bench is closed
Metadata storage0.0025 ALGO + 0.0004 ALGO per byteOnly when Terms & Conditions or restricted countries are set
Network fees~0.02 ALGOCovers the inner transactions the registry sends to deploy and configure the bench

When selling ALGO, the amount for sale is added to the same payment.

The platform fee on sales is locked into the bench at creation from the registry's default_fee_bps (currently 15 bps, 0.15%). Later changes to the registry default do not affect existing benches.

What happens on-chain

Creation is one atomic transaction group sent to the registry:

  1. Payment of the setup fee + escrow seed (+ amount for sale, when selling ALGO) to the registry
  2. When selling an ASA: a registry optin(asset) call, followed by an asset transfer of the amount for sale to the registry
  3. The registry create(...) call

The registry then, through inner transactions:

  • Verifies both assets against the allow and ban lists
  • Deploys a new bench contract and opts it into the asset(s)
  • Seeds the bench with ALGO and transfers the tokens for sale into it (the registry does not keep any of your tokens: it closes its temporary holding out to the bench)
  • Assigns the next bench ID, emits a Created(uint64,uint64) event with the bench ID and app ID, and calls setup on the bench with the owner, fee settings and sale parameters
  • Records the bench in its directory (see Storage)

The create method returns (bench_id, app_id).

Managing a Bench

The bench owner manages a bench from its page on myth.finance/bench ("Manage"), or with the TypeScript SDK. Unless noted otherwise, each action below can be performed by the owner or by the Myth Finance fee admin (see Roles). Proceeds always go to the owner, regardless of who calls.

Withdrawing proceeds

withdraw(amount) sends amount of the purchase asset from the bench to the owner. Available at any time, including while the sale is running. The withdrawable amount is the bench's purchase asset balance minus the platform fees accrued so far.

Editing metadata

Owner only. Terms & Conditions and restricted countries can be changed or removed at any time. See Metadata.

Ending a bench

end() stops the sale immediately. No more purchases are accepted; unsold tokens and proceeds stay in the bench until it is closed.

Benches also end by themselves when they sell out, or when their end time passes. See How it Works.

Deleting purchase-tracking boxes

Only relevant for benches with a purchase limit, which hold one box per buyer. After the bench has ended, and before it can be closed, these boxes must be deleted with delete_boxes(box_names). The frontend does this for you ("Delete boxes"). Deleting the boxes releases their minimum balance to the bench, which is paid out to the owner on close.

Closing a bench

close() is available once the bench has ended. It:

  1. sends the accrued platform fees to the fee admin,
  2. sends the entire remaining purchase asset balance (the proceeds) to the owner and opts the bench out of the purchase asset,
  3. sends the unsold tokens to the owner and opts the bench out of the asset for sale,
  4. closes the bench's ALGO balance (escrow seed and any released box minimum balances) to the owner.

The owner must be opted in to both assets for close to succeed. After closing, the bench contract is empty. It remains on-chain (bench contracts are not deletable) and is removed from the registry directory by the registry admin.

Withdrawing platform fees

Fee admin only. withdraw_fees(amount) sends up to the accrued fee amount to the fee admin. Fees not withdrawn during the sale are paid out on close.

Lowering the fee

Fee admin only. lower_fee(new_fee_bps) can lower, and only lower, the platform fee of an individual bench.

Metadata: Terms & Restricted Countries

Besides its immutable sale parameters, every bench has an editable metadata string, stored in the bench's registry directory entry (up to 1014 bytes). Myth Bench uses it as a JSON object with the following optional keys:

KeyTypeDescription
tcstring (URL)Terms & Conditions link
bcstring[]Restricted countries, as ISO 3166-1 alpha-2 codes (e.g. ["US", "KP"])

Example: {"tc":"https://example.com/token-sale-terms","bc":["US"]}

The SDK validates metadata objects (validateMeta): only these two keys are accepted, bc must be a non-empty array of strings when present, and the object may not be empty.

Terms & Conditions

When set, the frontend shows a prominent "Make sure to read: Distribution Terms & Conditions" notice linking to the URL on the bench page.

Restricted countries

When set, the frontend geolocates visitors by IP address (browser side) and shows a "Restricted Access" page instead of the bench to visitors from the listed countries.

Country restrictions are enforced by the myth.finance frontend only. They are not enforced by the smart contract: anyone can interact with a bench directly, and other frontends may not honour the list. Sellers remain responsible for the compliance of their sales.

Setting and updating metadata

Metadata is set at creation, and can be updated or removed at any time by the bench owner, from the "Manage" page or with the SDK (setMeta). Updates go through the registry (set_meta), which verifies that the caller is the bench's owner. Passing an empty string removes the metadata.

Metadata is not part of the bench contract's own state: it is read through the registry (get_meta, get_state_with_meta, log_bench_states_with_meta).

Asset Eligibility

To reduce the risk of scam tokens, the registry checks both assets of a new bench (the asset for sale and the purchase asset) against on-chain asset labels before creating it. ALGO is always allowed.

Labels are read from an ABEL asset labeling contract (abel_app_id in the registry). The registry keeps two lists of labels:

  • Allowlist (allowlist_labels): an asset must carry at least one of these labels. An empty allowlist disables this check.
  • Banlist (banlist_labels): an asset carrying any of these labels is rejected, regardless of the allowlist.

Current mainnet configuration:

ListLabelMeaning
AllowlistpvPera Verified
AllowlistmfMyth Finance allowlist
BanlistmbMyth Finance banlist

In other words, on mainnet a bench can be created for ALGO and any Pera Verified ASA (or one explicitly allowlisted by Myth Finance), unless it has been banned.

The create form on myth.finance only offers eligible assets from your wallet; ineligible assets are listed but disabled.

The registry admin can change the label lists and the labeling contract at any time (see Roles). Eligibility is only checked at creation: existing benches are unaffected by later changes.

If you would like your asset allowlisted, contact us.

Pricing & Amounts

Price representation

Every bench stores a single integer price, purchase_price_per_100m: the amount of the purchase asset, in its base units, required to buy 100,000,000 base units of the asset for sale.

For a purchase spending spend base units of the purchase asset, the bench sends

tokens = ⌊ spend × 100,000,000 / purchase_price_per_100m ⌋

base units of the asset for sale.

To convert a human-readable price to the on-chain value:

purchase_price_per_100m = price × 10^purchase_decimals × 100,000,000 / 10^selling_decimals

where price is the amount of purchase asset per whole unit of the asset for sale.

Example: selling ALPHA (6 decimals) at 0.009405 USDC (6 decimals) each: purchase_price_per_100m = 0.009405 × 10^6 × 10^8 / 10^6 = 940,500. Spending 10 USDC (10,000,000 base units) buys ⌊ 10,000,000 × 100,000,000 / 940,500 ⌋ = 1,063,264,221 base units, i.e. 1,063.264221 ALPHA.

The price must be greater than zero. The intermediate multiplication cannot overflow; the resulting token amount must fit in a 64-bit integer.

Rounding

Token amounts are rounded down. Fractions of a base unit stay in the bench in favour of the seller. Purchases that would round down to zero tokens are rejected (ERR:ZERO AMT).

Dust and automatic end

The least purchasable amount of a bench is the number of tokens (in base units) that the smallest unit of the purchase asset buys, rounded up. When a sale leaves fewer tokens than this in the bench, nobody could buy them, so the bench ends automatically: its time_end is set to the time of that last sale. Any such dust is returned to the owner on close.

Similarly, if no start time was configured, the bench's time_start is set to the time of the first sale.

Purchase limits

Purchase limits are expressed in base units of the purchase asset, and are cumulative per account for the lifetime of the bench. See Buying.

Fees

Platform fee on sales

Each purchase accrues a platform fee inside the bench, calculated in the purchase asset:

fee = spend × fee_bps / 10,000

fee_bps is copied into the bench from the registry's default_fee_bps at creation and cannot be raised afterwards; the fee admin can only lower it for an individual bench. The current mainnet default is 15 bps (0.15%). The live value can be read from the registry's global state, see Deployments.

The fee is borne by the seller: buyers receive the full amount of tokens for what they spend, and the fee is deducted from what the owner can withdraw. Fees remain in the bench escrow (fees_accrued) until the fee admin withdraws them, or the bench is closed.

Setup fee

A one-off fee in ALGO, paid to the registry when creating a bench. It is set by the registry admin (setup_fee) and is currently 20 ALGO on mainnet. See Creating a Bench for the full cost breakdown, including the refundable escrow seed.

Transaction fees

  • Buyers pay their own transaction fees, plus 0.001 ALGO for the inner transaction that delivers their tokens.
  • Sellers pay the transaction fees of creation (~0.02 ALGO, mostly inner transaction fees) and of management actions (withdraw, end, close: 0.001–0.004 ALGO extra for inner transactions).
  • The bench contract never spends its own ALGO on fees; its escrow seed is returned intact to the owner on close.

Minimum balance requirements

  • Bench escrow: 0.1 ALGO, plus 0.1 ALGO per ASA opt-in (0.2 or 0.3 ALGO in total). Funded by the creator, refunded to the owner on close.
  • Purchase-tracking boxes (benches with a purchase limit): 0.0185 ALGO per buyer. Funded by each buyer on their first purchase, released to the owner on close.
  • Registry directory entry: funded by the creator as part of the setup payment. Metadata increases it by 0.0004 ALGO per byte.

Immutability

Bench contracts

Bench contracts are immutable and non-deletable: they have no update or delete handlers, and their configuration is written exactly once, by the registry, at creation (setup). No one, including Myth Finance, can change a bench's price, amount, assets, schedule or owner after creation, or move funds anywhere other than to buyers (tokens), the owner (proceeds, unsold tokens, ALGO seed) and the fee admin (accrued platform fees).

The only mutable aspects of a bench are its metadata (owner) and its platform fee, which can only be lowered (fee admin).

Registry contract

The registry is upgradable and deletable by the registry admin, without a time-lock. Upgrades affect how future benches are created and how the directory is served; they cannot alter existing benches, which are independent contracts. The registry does not custody user funds beyond setup fees and, transiently within the creation transaction group, the tokens being moved into a new bench.

The ABI specifications published here correspond to the registry contract currently deployed on mainnet, and to the bench contract it deploys.

Roles

Registry admin

A Myth Finance controlled account (admin_addr in the registry). Can:

  • Upgrade or delete the registry contract (see Immutability)
  • Change the admin address, in a two-step handover: change_admin_1(new_admin) by the current admin, immediately followed by change_admin_2() from the new admin, in the same group
  • Change the fee admin address (change_fee_addr)
  • Change the default platform fee for new benches (change_default_fee_bps)
  • Change the setup fee (change_setup_fee)
  • Change the asset labeling contract and the allow/ban label lists (change_abel_app_id, change_allowlist_labels, change_banlist_labels), see Asset Eligibility
  • Increase the bench ID counter (set_id_counter)
  • Remove closed benches (with a zero balance) from the directory (cleanup)

None of these affect the sale parameters or the funds of existing benches.

Fee admin

A Myth Finance controlled account (fee_addr in the registry, copied into each bench at creation). Per bench, can:

  • Withdraw accrued platform fees (withdraw_fees)
  • Lower the bench's platform fee (lower_fee)
  • Perform the owner's management actions on the owner's behalf: withdraw (proceeds always go to the owner), end, delete_boxes, close

The fee admin cannot redirect proceeds or tokens anywhere other than the owner, and cannot change sale parameters.

Bench owner

The beneficiary chosen at creation (owner_addr, defaults to the creator). Can:

  • Withdraw proceeds (withdraw)
  • End the sale (end)
  • Delete purchase-tracking boxes after the end (delete_boxes)
  • Close the bench (close)
  • Update the bench metadata (set_meta on the registry)

Buyers

Anyone can buy from an open bench, subject to its purchase limit.

Contract Storage

Registry

Global state

KeyTypeDescription
admin_addraddressRegistry admin
fee_addraddressFee admin, copied into new benches
default_fee_bpsuint64Platform fee for new benches, in basis points
setup_feeuint64Setup fee for new benches, in microALGO
abel_app_iduint64Asset labeling app used for eligibility checks; 0 disables the checks
allowlist_labelsstring[] (ABI-encoded)See Asset Eligibility
banlist_labelsstring[] (ABI-encoded)See Asset Eligibility
id_counteruint64Last assigned bench ID

Boxes: the bench directory

One box per bench. The box name is the ABI-encoded RegistryKey struct (44 bytes) and the box value the ABI-encoded RegistryValue:

RegistryKey {
  id:                      uint32   // bench ID
  original_time_start:     uint32   // configured start time, 0 = none
  original_time_end:       uint32   // configured end time, 0 = none
  selling_asset_id:        uint64   // 0 = ALGO
  selling_amount:          uint64
  purchase_asset_id:       uint64   // 0 = ALGO
  purchase_price_per_100m: uint64
}

RegistryValue {
  bench_app_id: uint64
  meta:         string   // up to 1014 bytes, see Metadata
}

Since the key contains the sale parameters, listing the registry's boxes (algod GET /v2/applications/{registry_app_id}/boxes) is enough to enumerate all benches with their basic parameters, without reading each bench.

Bench

Global state

KeyTypeDescription
iduint64Bench ID
owner_addraddressOwner (beneficiary)
fee_addraddressFee admin
fee_bpsuint64Platform fee, in basis points
fees_accrueduint64Platform fees accrued and not yet withdrawn, in purchase asset base units
selling_assetuint64Asset for sale, 0 = ALGO
selling_amountuint64Total amount for sale
remaining_amountuint64Amount left for sale
purchase_assetuint64Purchase asset, 0 = ALGO
purchase_price_per_100muint64Price, see Pricing
purchase_limituint64Maximum spend per account in purchase asset base units, 0 = unlimited
time_startuint64Configured start time, or time of the first sale if none was configured
original_time_startuint64Configured start time, 0 = none
time_enduint64Configured end time, or actual end time once sold out or ended manually
original_time_enduint64Configured end time, 0 = none
cancellableuint64Whether the owner may end the bench before its end time. Always 1 for benches created by the current registry, see ABI notes
versionuint64Bench contract version, 2

Boxes: purchase tracking

Only used when purchase_limit > 0. One box per buyer account: the box name is the buyer's 32-byte public key, and the value the buyer's cumulative spend as a uint64, in purchase asset base units. Minimum balance requirement 0.0185 ALGO per box, funded by the buyer on first purchase.

Contract ABI

Bench is made up of two on-chain smart contracts. Their application specifications are published here in ARC-32 format. Use these specs to generate typed clients or to decode application calls, events and state.

ContractARC-32
Registry Contract — one per network, creates benches and keeps their directoryregistry.arc32.json
Bench Contract — one instance per salebench.arc32.json

For a ready-made TypeScript client generated from these specs, see the Typescript SDK.

Failed assertions log a short ERR:... message before rejecting, e.g. ERR:QUOTA (purchase limit exceeded), ERR:OVERSELL (not enough tokens left), ERR:NOT STARTED, ERR:ENDED, ERR:UNAUTH.

Registry Contract

Structs used below: RegistryKey = (uint32,uint32,uint32,uint64,uint64,uint64,uint64) and State (the bench state tuple returned by get_state), see Storage.

Public methods

MethodDescription
optin(asset)Opt the registry into the asset for sale. Must be immediately followed by the asset transfer and the create call in the same group.
create(account owner_addr, uint64 time_start, uint64 time_end, asset selling_asset, uint64 selling_amount, asset purchase_asset, uint64 purchase_price_per_100m, uint64 purchase_limit, uint64 cancellable, string meta) → (uint64,uint64)Create a bench. Returns (bench_id, app_id) and emits Created(uint64,uint64). See Creating a Bench.
set_meta(RegistryKey registry_key, string meta)Update a bench's metadata. Caller must be the bench owner.
get_meta(RegistryKey registry_key) → stringRead a bench's metadata.
get_registry_key(uint32 id, uint64 purchase_asset_id, uint64 purchase_price_per_100m, uint64 selling_asset_id, uint64 selling_amount, uint32 time_start, uint32 time_end) → RegistryKeyHelper to build a RegistryKey.
noop()No-op, used to attach additional resource references to a group.

Read-only methods

Intended for simulate calls. The log_* variants log one ABI-encoded value per key (an empty log for unknown keys), which allows batching many benches per call.

MethodDescription
get_app_ids(RegistryKey[] box_names) → uint64[]Bench app IDs for the given keys.
get_bench_states(RegistryKey[] box_names) → State[]Bench states for the given keys.
log_bench_states(RegistryKey[] box_names)Log the State of each bench.
log_bench_states_with_meta(RegistryKey[] box_names)Log the StateWithMeta of each bench (state, metadata, and the caller's cumulative spend).
log_bench_meta(RegistryKey[] box_names)Log the metadata of each bench.

Privileged methods

Registry admin only, see Roles.

MethodDescription
change_admin_1(account new_admin) / change_admin_2()Two-step admin handover.
change_fee_addr(account new_fee_addr)Change the fee admin address.
change_default_fee_bps(uint64 new_default_fee_bps)Change the platform fee for new benches.
change_setup_fee(uint64 new_setup_fee)Change the setup fee for new benches.
change_abel_app_id(uint64 new_abel_app_id)Change the asset labeling contract.
change_allowlist_labels(string[] new_allowlist_labels)Change the allowlist labels.
change_banlist_labels(string[] new_banlist_labels)Change the banlist labels.
set_id_counter(uint64 new_id_counter)Increase the bench ID counter.
cleanup(account owner, uint32 id, uint32 time_start, uint32 time_end, asset selling_asset, uint64 selling_amount, asset purchase_asset, uint64 purchase_price_per_100m)Remove a closed bench (zero balance) from the directory.
Bare UpdateApplication / DeleteApplicationUpgrade or delete the registry.

Bench Contract

One instance is deployed per sale by the registry.

Public methods

MethodDescription
buy()Buy tokens. Preceded in the group by the payment (and by the box MBR payment on the first purchase from a bench with a purchase limit). See Buying.
get_account_buy_total(address account) → uint64Cumulative spend of an account, in purchase asset base units.

Read-only methods

MethodDescription
get_state() → StateFull bench state, including the unit names and decimals of both assets.
get_state_with_meta(application registry_app) → StateWithMetaAs above, plus the metadata (read from the registry) and the caller's cumulative spend.
get_meta(application registry_app) → stringThe bench's metadata, read from the registry.

Privileged methods

See Roles for who may call each.

MethodRoleDescription
withdraw(uint64 amount)owner / fee adminSend proceeds to the owner.
end()owner / fee adminEnd the sale.
delete_boxes(byte[][] box_names)owner / fee adminDelete purchase-tracking boxes after the end.
close()owner / fee adminPay out fees, proceeds and unsold tokens; opt out of assets; refund the ALGO seed to the owner.
withdraw_fees(uint64 amount)fee adminWithdraw accrued platform fees.
lower_fee(uint64 new_fee_bps)fee adminLower the platform fee of this bench.
optin(asset) / optin2(asset,asset2)registry (creator)Opt the bench into its assets, at creation.
setup(...)registry (creator)Configure the bench, once, at creation.

Notes

  • cancellable: the bench contract has a cancellable flag intended to prevent the owner from ending a bench that has an end time before that time. In the currently deployed contract version, setup requires cancellable = 1, so every bench created by the current registry can be ended early by its owner. The frontend and SDK always create cancellable benches.
  • All amounts are in base units. Prices are purchase_price_per_100m values, see Pricing.
  • Bench IDs are assigned sequentially by the registry (id_counter).

Deployments

Mainnet

Value
Bench Registry app ID3194114544
Bench Registry addressKFEJ4YRV7MFBIKWRHQV3IKMG2YHBYEZPOSMB6PJRSTR4AYXPMYWZOHQXTY
Asset labeling (ABEL) app ID2914159523
Registry admin and fee adminMYTHJSGEPTU7EFDVUHTV6GLXYQLHNYG3HMXHPVS4T4C4XFPJRLMXILLORE

Current registry settings (as of August 2026; read them live from the registry's global state or with the SDK's getRegistryState()):

SettingValue
default_fee_bps15 (0.15%)
setup_fee20000000 (20 ALGO)
allowlist_labelspv, mf
banlist_labelsmb

Bench instances are separate applications, one per sale. Find them through the registry directory (storage) or the SDK. Bench IDs 0–4 predate the current registry: they were created under earlier registry deployments and are served by the SDK's built-in overrides (overrides: true).

The myth.finance frontend is configured with VITE_BENCH_REGISTRY_APP_ID=3194114544 and the public Nodely endpoints https://mainnet-api.4160.nodely.dev (algod) and https://mainnet-idx.4160.nodely.dev (indexer).

Testnet

Value
Bench Registry app ID744812582
Asset labeling (ABEL) app ID744813173

The testnet registry has no labeling contract configured (abel_app_id = 0), so asset eligibility checks are disabled there. Frontend: testnet.myth.finance/bench

Typescript SDK

Developers can use the published @myth-finance/bench-sdk package to read and interact with Bench contracts programmatically. It wraps typed clients generated from the ABI specs, and takes care of transaction group composition, asset and box references, minimum balance payments and state decoding.

The SDK has complete support for all operations that the contracts support: reading the directory and bench states, buying, creating and managing benches, as well as fee admin and registry admin operations.

npm install @myth-finance/bench-sdk @algorandfoundation/algokit-utils algosdk p-map

Peer dependencies: @algorandfoundation/algokit-utils ^9, algosdk ≥ 3.4, p-map. Both an ESM and a CommonJS build are published.

Clients

import { AlgorandClient } from "@algorandfoundation/algokit-utils";
import { BenchRegistryClient, BenchInstanceClient } from "@myth-finance/bench-sdk";

const algorand = AlgorandClient.fromConfig({
  algodConfig: { server: "https://mainnet-api.4160.nodely.dev", port: 443, token: "" },
});

const registryAppId = 3194114544n; // mainnet, see Deployments

const registry = new BenchRegistryClient({ registryAppId, algorand, overrides: true });
const bench = new BenchInstanceClient({ instanceAppId: 3277191702n, registryAppId, algorand, overrides: true });

Constructor options, common to both clients:

OptionDescription
algorandAn AlgorandClient. Note that the SDK sets its default signer to an empty signer and configures its suggested params cache.
readerAccountAddress used as the sender of read-only simulate calls. Defaults to a well-known account.
overridesInclude historical mainnet benches (IDs 0–4, created before the current registry) from built-in fixtures, so that listings are complete. Default false.
paramsCacheTimeoutSuggested params cache duration, in ms.

The generated low-level clients are available as registry.client and bench.client for anything not covered by the SDK.

Reading

// registry settings: fees, eligibility labels, id counter
const settings = await registry.getRegistryState();

// all benches, keyed by bench app ID
const benches = await registry.getBenchSmallStates();
for (const [appId, b] of benches) {
  console.log(b.benchId, `${b.sellingUnitName}/${b.purchaseUnitName}`, b.price, b.remainingTokens, b.timeEnd);
}

// one bench, including its metadata and the caller's remaining purchase allowance
const state = await bench.getFullState(/* optional caller address */);
  • getRegistryKeys() enumerates the registry's directory boxes and decodes them into RegistryKeys.
  • getBenchSmallStates(keys?) fetches bench states in batches of 42 per simulate call, using the registry's log_bench_states method, and returns a Map<appId, BenchStateSmall>.
  • getFullState(sender?) calls get_state_with_meta on the bench and returns a BenchStateFull, which adds meta, caller, userMaxSpend (the caller's remaining spend allowance, in purchase asset base units) and userNeedMBR (whether the caller's next purchase requires the box minimum balance payment) to the listing fields.

Amount fields: price, total and remainingTokens are in whole units (decimals applied); sellingAmount, purchaseLimit, feesAccrued, userMaxSpend and purchasePricePer_100m are in base units.

Signing

Methods that send transactions take a sender (an algosdk Address) and a signer (an algosdk TransactionSigner), for example an account's signer from AlgoKit Utils, or the transaction signer provided by a wallet library:

import { Address } from "algosdk";
const sender = Address.fromString("YOUR-ADDRESS");
const signer = /* TransactionSigner */;

Every action also has a make…Txns counterpart (makeBuyTxns, makeCreateTxns, makeWithdrawTxns, ...) that returns the transaction group composer without sending it, which can be used to build, simulate or sign transactions in your own flow:

const group = await bench.makeBuyTxns({ amount, sender, signer });
const { transactions } = await (await group.composer()).buildTransactions();

Buying

await bench.buy({ amount: 10_000_000n, sender, signer }); // spend 10 USDC (base units)

amount is the spend, in base units of the purchase asset. The SDK adds an opt-in to the asset for sale and the box minimum balance payment to the group when needed. See Buying.

Creating a bench

const { instanceAppId } = await registry.create({
  owner: sender,                     // beneficiary, see Creating a Bench
  sellingAsset: 2726252423n,         // 0n for ALGO
  sellingAmount: 1_000_000_000_000n, // base units
  purchaseAsset: 31566704n,          // 0n for ALGO
  purchasePricePer_100m: 940_500n,   // see Pricing
  timeStart: 0n,                     // unix seconds, 0n = open immediately
  timeEnd: 0n,                       // unix seconds, 0n = no end time
  cancellable: true,
  purchaseLimit: 0n,                 // purchase asset base units, 0n = unlimited
  meta: { tc: "https://example.com/terms", bc: ["US"] }, // optional, see Metadata
  sender,
  signer,
});

The SDK reads the registry settings, computes the required ALGO payment (setup fee, escrow seed, metadata storage) and composes the full creation group.

Managing a bench

await bench.withdraw({ amount, sender, signer });
await bench.end({ sender, signer });

const boxNames = await bench.getPurchaseBoxNames();
if (boxNames.length) await bench.deleteBoxes({ boxNames, sender, signer });

await bench.close({ sender, signer });

// update or remove metadata (meta: null removes it)
await registry.setMeta({ registryKey: await bench.getRegistryKey(), meta: { tc: "https://example.com/terms" }, sender, signer });

Fee admin: bench.withdrawFees. Registry admin: registry.changeFee, changeSetupFee, changeFeeAddr, changeAbelAppId, changeAllowlistLabels, changeBanlistLabels, setIdCounter, changeAdmin, cleanup.

Types

The package exports the BenchStateSmall, BenchStateFull, BenchMeta, RegistryState and WithSigner types, and the validateMeta helper. BenchRegistryClient.getRegistryKey(state) and bench.getRegistryKey() provide the registry key that addresses a bench in the registry directory (see Storage).

Links

Bench mainnet

Bench mainnet (backup)

Bench testnet

Documentation ⬅ You are here

TypeScript SDK on npm

dualSTAKE documentation


For contact details see Contact

Contact

Email: info@myth.finance

Security contact: security@myth.finance PGP key available upon request.

Social Media

We are on X.com as @Myth_Finance. This is the primary place to get updates, and you can DM us for support.

Discord-wise, we are generously hosted on the premier user community for DeFi on Algorand, Dragon Fi. You can use this invite link which should take you to the Myth Finance > General Chat channel.

We are not on Telegram or Reddit as of yet.