Gülçin Uras
Tinyman JS SDK hero image
( Loading )
03

Tinyman JS SDK

Tinyman JS SDK is an open source TypeScript library that the Tinyman web client uses to interact with Tinyman's smart contracts. When the contracts began storing data in Algorand boxes, a shared base class was built to decode the binary data into typed values. The liquid staking and order clients extend that class.

Year
2021 - 25
Role
Frontend Developer
Stack
TypeScript, JavaScript, algosdk, Algorand, Jest
Highlights

TinymanBaseClient

Level 3 · Base client helpers
TinymanBaseClient<AppId, AppAddress> · abstractapp infobox by nameaccount assetFee poolingsetupTxnFeeAndAssignGroupIdGroup fee on txn[0],covers inner txnsMinimum balancecalculateMinBalanceAccount, asset andbox MBRSigner transactionsconvertStandardTransactions…Wallet-ready groups,signer count checkedGlobal stategetGlobalThis app's oranother app's stateBoxesboxExists, getBoxDecoded into structs,null when missingOpt-insgetOptinTxnIfNeededOpt-in txn onlywhen it's neededAlgorandalgodApp, box andaccount reads
TinymanBaseClientAlgorand + GitHubClick the canvas, the path above, or Esc to zoom out

Level 3 · Base client helpers: TinymanBaseClient

Each app client was repeating the same chain work: fees, state reads, boxes, minimum balance and opt-ins. With one copy, a fix to fee or balance maths reaches every client.

An abstract class that the SDK's app clients extend. It holds algod, the app ID and address, the network and the box struct definitions, plus the chain helpers each client needs. It doesn't keep box contents: each read fetches the box and decodes its bytes into typed values.

The app ID is generic, so it can be null for apps that may not exist yet, like a user's order app. The constructor can also take structs, which describe how a box's binary data is laid out: where each field starts, how many bytes it takes and what kind of value it holds. getBox fetches a box by name, reads it using its struct and returns null when the box doesn't exist. getGlobal reads a value from a smart contract's global state, either the client's own contract or another one. Fee pooling, minimum balance and opt-in helpers live in the same class.

TinymanTAlgoClient, TinymanSTAlgoClient and OrderingClient extend it. The shared chain logic lives in one class, so each client only defines its own app calls instead of copying fee, box and opt-in code.

A struct definition maps each field to an offset, a size and a type. Struct wraps the raw box bytes in a DataView and decodes nothing up front: getField slices the field's bytes on read and decodes them by type. Integers become bigint read big-endian, bytes stay as a Buffer, and a field can be another struct or a fixed-length array of structs (Name[n]), so nested data decodes the same way. Struct sizes also give each box's minimum balance in calculateMinBalance. Staking clients use TinymanBaseClient<number, Address>; OrderingClient uses <number | null, Address | null>.

OrderingClient

Level 3 · OrderingClient
extendsprogramsentry + versionput_*_ordercancel / collectTinymanBaseClient<number | null, …>appId stays null untilthe order app existsOrderingClientinitializeOrderingClientFinds the user's appthrough the registryOrder programsGitHubtinyman-order-protocol,approval + clearOrder app setupcreate + updateCreate, check version,update → verify → postPut orderstrigger + recurringMBR top-up, opt-ins,transfer, put callExisting orderscancel + collectReads the order box,then cancel or claimRegistry appAlgorandEntry boxes, latestversion, fee ratesUser's order appone per accountOrder boxes,version
TinymanBaseClienttinyman-js-sdkAlgorand + GitHubClick the canvas, the path above, or Esc to zoom out

Level 3 · OrderingClient: OrderingClient

Tinyman added trigger and recurring orders through a new order protocol where each user has their own order app. The web client, and any other app using the SDK, needed a way to set up that app and place orders without building the transaction groups by hand. Moreover, the contracts' state had to be read for the version check, so each user can update their own order app whenever there is an update in the contract, and for the TINY power threshold that decides their fee rate. Reading these from the contract instead of hard-coding them means contract changes don't need an SDK release.

The client for limit (trigger) and DCA (recurring) orders: create and update the user's order app, put orders, cancel them, claim collected amounts and estimate fees.

initializeOrderingClient reads the registry app's "e" + public key box to find the user's order app; the app ID stays null if there isn't one. Creating the app pays the registry entry's minimum balance if needed, creates the app from compiled programs fetched from the tinyman-order-protocol repo, and registers it. shouldUpdateOrderingApp compares the registry's latest_version with the app's version, and the update is one update, verify, post-update group. Putting an order adds a minimum balance top-up for a new order box and asset opt-ins on the order app when needed, then the input transfer and the put call.

Every action is one prepared transaction group the app signs once. getPlatformFeeRate returns the governor fee rate for accounts at or above the TINY power threshold. The web client's Limit and DCA tabs share one OrderingClient per account.

The constructor is private; initializeOrderingClient looks up the app first, then builds the client. Methods are grouped by stage: app setup (prepareCreateOrderAppTransactions, shouldUpdateOrderingApp, prepareUpdateOrderingAppTransactions), placing orders (preparePutTriggerOrderTransactions, preparePutRecurringOrderTransactions, getPutTriggerOrderTransactionFee) and existing orders (prepareCancelOrderTransactions, prepareClaimCollectedTargetAmount). Each one reads boxes and state through the base class and returns transactions; signing stays with the caller.

Liquid staking: tAlgoClient and stAlgoClient

Level 3 · Liquid staking clients
extendsextendsmint / burnstake callsforeign appTinymanTAlgoClientALGO ↔ tALGOmint, burn,getRatioTinymanBaseClient<number, Address>Fee pooling, opt-ins,boxes, min balanceTinymanSTAlgoClienttALGO ↔ stALGOincreaseStake, decrease-Stake, claimRewardsAdded when neededbefore each callapply_rate_change, boxpayment, opt-insStake apptALGORate and stakingaccounts in stateVault appTINY powerUser boxes, referencedby restake callsRestake appstALGOUser boxes,reward rate
tinyman-js-sdkTinymanBaseClientAlgorand + GitHubClick the canvas, the path above, or Esc to zoom out

Level 3 · Liquid staking clients: Liquid staking

The contracts have preconditions: an up-to-date reward rate, a funded user box, opt-ins. Handling them in the SDK means callers make one call and don't need to know about them. calculateIncreaseStakeFee and calculateDecreaseStakeFee count those extra transactions, so the UI can show the exact fee before signing.

TinymanTAlgoClient mints tALGO for ALGO and burns it back. TinymanSTAlgoClient restakes tALGO into stALGO, unstakes it, and claims TINY rewards.

mint sends an opt-in if needed, the ALGO payment and the mint call, which references the four staking accounts read from global state. getRatio reads the rate from global state. Each stALGO operation first adds an apply_rate_change call when the reward period has ended, a payment for the user's box if it doesn't exist yet, and any opt-ins, then calls the restake app with the vault app and the user's boxes in both apps.

Every staking operation is one transaction group and one wallet approval. TinymanTAlgoClient has seven Jest tests against testnet covering mint and burn transactions, opt-ins, fees and the amounts received.

Both clients extend TinymanBaseClient with their app ID set from the network, and use its fee pooling, opt-in and box helpers. tALGO groups need one extra fee for inner transactions; stALGO groups need two or three. The user's restake box is named by their public key and decoded with a USER_STATE struct; its minimum balance comes from the base class's calculateMinBalance.

Next project — 01Silk