
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
TinymanBaseClient
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>.
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: 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.
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: 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.
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.