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).