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:
- A seller creates a bench, choosing the asset to sell, the purchase asset (
ALGOor any ASA), the amount and the price. - The Bench registry deploys a dedicated escrow contract for the sale and moves the tokens into it.
- Buyers send the purchase asset to the bench and instantly receive tokens at the fixed rate, atomically.
- The seller withdraws proceeds at any time, and closes the bench once it is over.
Features
- Sell
ALGOor any ASA, priced inALGOor 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
ALGOwhen creating a bench (currently 20ALGOon 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
| Role | Who | What |
|---|---|---|
| Creator | Any account | Pays for the creation and supplies the tokens |
| Owner (beneficiary) | Chosen by the creator, defaults to the creator | Receives proceeds, manages the bench and its metadata |
| Buyer | Any account | Buys tokens |
| Fee admin | Myth Finance | Withdraws platform fees, can lower a bench's fee, can perform management actions on behalf of the owner |
| Registry admin | Myth Finance | Platform-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
| # | Transaction | Notes |
|---|---|---|
| 1 | Asset opt-in | Only if you are not yet opted in to the token being sold |
| 2 | Payment of 0.0185 ALGO to the bench | Only for benches with a purchase limit, on your first purchase |
| 3 | Payment or asset transfer to the bench | The amount you spend |
| 4 | buy() application call | Extra 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
| Parameter | Description |
|---|---|
| Asset for sale | ALGO or an ASA you hold. Must pass the eligibility checks. |
| Purchase asset | ALGO or the ASA buyers will pay with. Must pass the eligibility checks, and differ from the asset for sale. |
| Amount for sale | Transferred into the bench escrow at creation. |
| Price | Purchase 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:
| Item | Amount | Notes |
|---|---|---|
| Setup fee | Registry setting setup_fee, currently 20 ALGO on mainnet | Platform fee, non-refundable |
| Escrow seed | 0.2 ALGO if one side is ALGO, 0.3 ALGO for two ASAs | Minimum balance of the bench account and its asset opt-ins. Refunded to the owner when the bench is closed |
| Metadata storage | 0.0025 ALGO + 0.0004 ALGO per byte | Only when Terms & Conditions or restricted countries are set |
| Network fees | ~0.02 ALGO | Covers 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:
- Payment of the setup fee + escrow seed (+ amount for sale, when selling
ALGO) to the registry - When selling an ASA: a registry
optin(asset)call, followed by an asset transfer of the amount for sale to the registry - 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
ALGOand 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 callssetupon 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:
- sends the accrued platform fees to the fee admin,
- sends the entire remaining purchase asset balance (the proceeds) to the owner and opts the bench out of the purchase asset,
- sends the unsold tokens to the owner and opts the bench out of the asset for sale,
- closes the bench's
ALGObalance (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:
| Key | Type | Description |
|---|---|---|
tc | string (URL) | Terms & Conditions link |
bc | string[] | 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:
| List | Label | Meaning |
|---|---|---|
| Allowlist | pv | Pera Verified |
| Allowlist | mf | Myth Finance allowlist |
| Banlist | mb | Myth 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
ALGOfor 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.004ALGOextra for inner transactions). - The bench contract never spends its own
ALGOon fees; its escrow seed is returned intact to the owner on close.
Minimum balance requirements
- Bench escrow: 0.1
ALGO, plus 0.1ALGOper ASA opt-in (0.2 or 0.3ALGOin total). Funded by the creator, refunded to the owner on close. - Purchase-tracking boxes (benches with a purchase limit): 0.0185
ALGOper 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
ALGOper 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 bychange_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_metaon the registry)
Buyers
Anyone can buy from an open bench, subject to its purchase limit.
Contract Storage
Registry
Global state
| Key | Type | Description |
|---|---|---|
admin_addr | address | Registry admin |
fee_addr | address | Fee admin, copied into new benches |
default_fee_bps | uint64 | Platform fee for new benches, in basis points |
setup_fee | uint64 | Setup fee for new benches, in microALGO |
abel_app_id | uint64 | Asset labeling app used for eligibility checks; 0 disables the checks |
allowlist_labels | string[] (ABI-encoded) | See Asset Eligibility |
banlist_labels | string[] (ABI-encoded) | See Asset Eligibility |
id_counter | uint64 | Last 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
| Key | Type | Description |
|---|---|---|
id | uint64 | Bench ID |
owner_addr | address | Owner (beneficiary) |
fee_addr | address | Fee admin |
fee_bps | uint64 | Platform fee, in basis points |
fees_accrued | uint64 | Platform fees accrued and not yet withdrawn, in purchase asset base units |
selling_asset | uint64 | Asset for sale, 0 = ALGO |
selling_amount | uint64 | Total amount for sale |
remaining_amount | uint64 | Amount left for sale |
purchase_asset | uint64 | Purchase asset, 0 = ALGO |
purchase_price_per_100m | uint64 | Price, see Pricing |
purchase_limit | uint64 | Maximum spend per account in purchase asset base units, 0 = unlimited |
time_start | uint64 | Configured start time, or time of the first sale if none was configured |
original_time_start | uint64 | Configured start time, 0 = none |
time_end | uint64 | Configured end time, or actual end time once sold out or ended manually |
original_time_end | uint64 | Configured end time, 0 = none |
cancellable | uint64 | Whether the owner may end the bench before its end time. Always 1 for benches created by the current registry, see ABI notes |
version | uint64 | Bench 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.
| Contract | ARC-32 |
|---|---|
| Registry Contract — one per network, creates benches and keeps their directory | registry.arc32.json |
| Bench Contract — one instance per sale | bench.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
| Method | Description |
|---|---|
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) → string | Read 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) → RegistryKey | Helper 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.
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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 / DeleteApplication | Upgrade or delete the registry. |
Bench Contract
One instance is deployed per sale by the registry.
Public methods
| Method | Description |
|---|---|
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) → uint64 | Cumulative spend of an account, in purchase asset base units. |
Read-only methods
| Method | Description |
|---|---|
get_state() → State | Full bench state, including the unit names and decimals of both assets. |
get_state_with_meta(application registry_app) → StateWithMeta | As above, plus the metadata (read from the registry) and the caller's cumulative spend. |
get_meta(application registry_app) → string | The bench's metadata, read from the registry. |
Privileged methods
See Roles for who may call each.
| Method | Role | Description |
|---|---|---|
withdraw(uint64 amount) | owner / fee admin | Send proceeds to the owner. |
end() | owner / fee admin | End the sale. |
delete_boxes(byte[][] box_names) | owner / fee admin | Delete purchase-tracking boxes after the end. |
close() | owner / fee admin | Pay out fees, proceeds and unsold tokens; opt out of assets; refund the ALGO seed to the owner. |
withdraw_fees(uint64 amount) | fee admin | Withdraw accrued platform fees. |
lower_fee(uint64 new_fee_bps) | fee admin | Lower 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 acancellableflag intended to prevent the owner from ending a bench that has an end time before that time. In the currently deployed contract version,setuprequirescancellable = 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_100mvalues, see Pricing. - Bench IDs are assigned sequentially by the registry (
id_counter).
Deployments
Mainnet
| Value | |
|---|---|
| Bench Registry app ID | 3194114544 |
| Bench Registry address | KFEJ4YRV7MFBIKWRHQV3IKMG2YHBYEZPOSMB6PJRSTR4AYXPMYWZOHQXTY |
| Asset labeling (ABEL) app ID | 2914159523 |
| Registry admin and fee admin | MYTHJSGEPTU7EFDVUHTV6GLXYQLHNYG3HMXHPVS4T4C4XFPJRLMXILLORE |
Current registry settings (as of August 2026; read them live from the registry's global state or with the SDK's getRegistryState()):
| Setting | Value |
|---|---|
default_fee_bps | 15 (0.15%) |
setup_fee | 20000000 (20 ALGO) |
allowlist_labels | pv, mf |
banlist_labels | mb |
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 ID | 744812582 |
| Asset labeling (ABEL) app ID | 744813173 |
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:
| Option | Description |
|---|---|
algorand | An AlgorandClient. Note that the SDK sets its default signer to an empty signer and configures its suggested params cache. |
readerAccount | Address used as the sender of read-only simulate calls. Defaults to a well-known account. |
overrides | Include historical mainnet benches (IDs 0–4, created before the current registry) from built-in fixtures, so that listings are complete. Default false. |
paramsCacheTimeout | Suggested 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 intoRegistryKeys.getBenchSmallStates(keys?)fetches bench states in batches of 42 per simulate call, using the registry'slog_bench_statesmethod, and returns aMap<appId, BenchStateSmall>.getFullState(sender?)callsget_state_with_metaon the bench and returns aBenchStateFull, which addsmeta,caller,userMaxSpend(the caller's remaining spend allowance, in purchase asset base units) anduserNeedMBR(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
Documentation ⬅ You are here
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.