What this package is like to depend on
Last release 10 days ago
13 Aug 2026
Ships fairly regularly
a new release about every 4 months
Nearly every release is documented
notes for 5 of 5 stable releases
Nothing withdrawn
no release was ever pulled
8 months old
7 releases · first in 2025
7 releases in the last 12 months
see the full history below
Release timeline
7 releases · Dec 2025 to Aug 2026Releases
latest 7-
3.1.013 Aug 2026Release notes
Open source →Corrects argument encoding for
variadic,counted-variadicandmulti<...>, makesBalance.fromEgldexact, reaches the gas allowances and estimator that were previously unreachable, and puts every exception under one base type. Adds theskills/knowledge base. One source-breaking removal,AbiNotFoundException, is listed under Removed.Contract call arguments
- A trailing
variadic<...>accepts either form. Each trailing argument was validated against the item type, so passing a wholeVariadicValuenever matched. A singleVariadicValueand flat trailing items now encode identically. counted-variadic<...>emits itsu32count, omitted on the endpoint path, so the contract read the first item as the count.multi<...>works in any input position. It was treated as variadic on themulti<prefix and the text between the brackets re-parsed as one type name, raisingUnknown type: TokenIdentifier,BigUint. Amulti<A,B>is a fixed-arity group.multi<...>expands to one wire slot per member. The controller's encoder had no branch for it and concatenated the members into a single argument.
Balance
Balance.fromEgld(num)is exact for decimal literals. It converted through a fixed-point rendering of the double, so0.1produced100000000000000006and0.3produced299999999999999989. Binary-representable values were unaffected, which is why the error was easy to miss.Gas, configuration and status
- Guardian and relayer gas allowances apply even when an explicit gas limit is given, and an
IGasLimitEstimatorsupplied toSmartContractControlleris consulted. Both were unreachable: the controller returned as soon as a limit was present, and contract calls always supply one. - Proxy entrypoints forward their
NetworkProviderConfig.ProxyNetworkEntrypointand the Devnet/Testnet/Mainnet presets built their provider without it, so client name, headers, timeout, retry, throttle and cache had no effect; applying aclientNamealso dropped the throttle and cache policies. TransactionOnNetwork.isCompletedandTransactionStatus.isCompletedagree; they diverged fornot-executable-in-block. The terminal-state predicate isisFinal.- An empty
datano longer discards token transfers.createTransactionForTransferguarded on non-empty data but branched on non-null, soUint8List(0)alongside transfers produced a native-EGLD transaction and the transfers vanished silently.
Errors
- Every exception the SDK raises descends from
AbidockException, so oneon AbidockExceptionclause catches them all. Nine types implementedExceptiondirectly and escaped it. AbiNotFoundExceptionremoved — it could never be raised. An ABI-less controller asked for an ABI throwsStateError, as the equivalent factory methods already did.
Removed
Removed Use instead AbiNotFoundExceptionStateError-- raised when an ABI-less controller is asked for oneTooling
abidock --help,-handhelpall print usage and exit successfully.abidock generate <abi> <output> <name>honours its arguments. The command looked for a config file first, so anabidock.yamlin the working directory made it regenerate that config's contracts and discard the three arguments silently. Precedence is now--config, then explicit arguments, then a discovered config.- Event polling no longer retries a rate-limited request indefinitely. After its bounded retries it skips the poll and resumes on the next cycle, as the log message always claimed.
Docs
- New
skills/knowledge base: 14 task-oriented reference files covering the public API, wallets and signing, transactions, contracts, ABI codecs, codegen, providers, ESDT, events, errors, Supernova and pitfalls, indexed byskills/README.md.AGENTS.mdat the repository root points agents at it. - The error-handling page no longer documents
AbiNotFoundException.
Migration from 3.0.0
Bump to
^3.1.0. The only source-breaking change isAbiNotFoundException: replace anyon AbiNotFoundExceptionclause withon StateError. Expect higher gas limits on guarded and relayed contract calls, because those allowances now apply when you pass an explicit limit. - A trailing
-
3.0.013 Aug 2026Release notes
Open source →Lands the top-level
NetworkEntrypointfaçade, reshapes the generated DTO surface, and corrects a set of defects that produced transactions the chain rejects. Removed API is listed under Removed; each entry has a working replacement. Migration is mostly mechanical: regenerate, swapStringtoken-identifier/address fields for the wrapper types, and walk the compiler errors.Minimum SDK is now Dart 3.13.
Entrypoints
New
lib/src/entrypoints/network_entrypoint.dart, exported from the package barrel.NetworkEntrypoint/DevnetEntrypoint/TestnetEntrypoint/MainnetEntrypoint-- API-backed (indexer).ProxyNetworkEntrypoint/DevnetProxyEntrypoint/TestnetProxyEntrypoint/MainnetProxyEntrypoint-- Gateway-backed (chain-go Proxy).EntrypointUrlsconstants for the official public hosts.
Each entrypoint caches a single
NetworkProviderand exposescreateSmartContractController,createTransfersFactory,createTokenManagementFactory,createDelegationFactory,createMultisigFactory,createValidatorsFactory,createGovernanceFactory,create*ControllerandcreateTransactionWatcher. Documented atdocs/docs/network/entrypoints.md.Auto-gas signing bug fixed in generator
Generated auto-gas calls signed the transaction and then replaced its gas limit with
copyWith(newGasLimit:), which invalidated the signature. The generator now builds an unsigned probe viaSmartContractCallFactory, simulates gas, and callscontroller.callonce with the final value, so signing happens against the gas that ships. Regenerate existing output to pick up the fix.Type-mapper changes (generated DTO breaking change)
The codegen
TypeMappernow emits the wrapper types instead ofString:ABI type Old Dart type New Dart type AddressStringAddressTokenIdentifierStringTokenIdentifierEsdtTokenIdentifierStringTokenIdentifierEgldOrEsdtTokenIdentifierStringEgldOrEsdtTokenIdentifierBigFloat(unsupported) doubleManagedByteArray<N>(unsupported) Uint8ListMultiValue<...>(unsupported) record (T1, T2, ...)Generated struct fields, query return types and call arguments shift accordingly. Where
pair.firstTokengave aStringit now gives aTokenIdentifier-- use.valuefor the raw string.Transactions the chain rejected
- ESDT built-in functions are addressed to the sender, not the ESDT system contract, because they execute against the caller's own account:
ESDTNFTCreate,ESDTLocalMint,ESDTLocalBurn,ESDTNFTUpdateAttributes,ESDTNFTAddQuantity,ESDTNFTBurn,ESDTModifyRoyalties,ESDTSetNewURIs,ESDTModifyCreator,ESDTMetaDataUpdate,ESDTMetaDataRecreate,ESDTNFTAddURI,ESDTNFTUpdate,ESDTNFTRecreate. The other 23 endpoints still target the system contract. - Governance contract address corrected from
…0006ffffto…0003ffff. Every governance transaction went to an account that is not a contract, andcreateTransactionForNewProposalstranded its 1000 EGLD deposit there. - Validator operations target the validator contract (
…0001ffff) instead of the staking contract (…0000ffff), which only accepts calls from the validator contract — wallet-signed staking transactions could never succeed. changeConfigencodesminQuorum,minVetoThresholdandminPassThresholdas ASCII decimal, which is what the contract parses; they were sent as big-endian integers.clearEndedProposalsgas scales with the proposer count (gasLimit + n * gasLimit) instead of a flat limit.registerAndSetAllRolesandregisterDynamicemit the mandatory token type, which both omitted;registerAndSetAllRolesDynamicappendsnumDecimalsonly forMETA.- Token properties are written in full. Only enabled flags were emitted, but a missing pair does not mean "disabled" — the contract creates tokens with
canUpgradeandcanAddSpecialRolesalready on and overrides only the properties the arguments name, soTokenProperties(canUpgrade: false)produced an upgradable token andcontrolChangescould not switch a property off. The fungibleissueendpoint omitscanTransferNFTCreateRole, which is not part of its argument list. - Factories add the data-movement gas term (
minGasLimit + gasLimitPerByte * data.length) on top of execution gas. Token-management, delegation and the guardian builders shipped the bare execution limit, under-charging by an amount that grew with the payload.
Signing and relayed transactions
innerTransactionsno longer reaches the signing payload. The field is not part of the chain's transaction format, so every relayed transaction was signed over a payload the node cannot reconstruct and the signature could not verify. Removed fromTransactionentirely.RelayedTransactionsFactoryimplements flat relayed v3: one transaction carryingrelayerandrelayerSignature. Attach the relayer withapplyRelayerbefore signing, then sign with the sender and the relayer in either order.Transaction.serializeForSigning()applies the Keccak digest when the hash-signing option bit is set.signWith,signAsRelayerandsignAsGuardiansigned the raw payload, producing signatures the chain rejects.UserPublicKey.verifyreturnsfalsefor malformed signatures instead of throwing; the result was returned withoutawaitinside thetry, socatchnever saw asynchronous errors.Account.fromMnemonicdisposes the mnemonic after key derivation completes, not before.
Outcome parsers
TokenManagementOutcomeParserreads the logs of the transaction's smart-contract results as well as its own.freeze,unFreeze,wipe,setSpecialRole,unSetSpecialRoleand the local mint/burn pair act on another account by forwarding a built-in call, so their events land on the result rather than the transaction. Every such parse returned an empty list, and asignalErrorreported on a result was invisible — a failed transaction parsed as a successful empty outcome.Network providers
TransactionOnNetwork.fromApiResponsereads smart-contract results fromresults; they were alwaysnull.relayedVersionis aString?('v1','v2','v3'); it was parsed as an integer and threw on every relayed transaction.- Corrected routes: transaction simulation, guardian data, and the gateway's non-fungible token listing, which pointed at a path no node or proxy serves.
- Guardian fields are read from the flat account payload, and the provider sends the query flag required to return them.
GatewayNetworkProvideraccepts aNetworkProviderConfig, so user agent, timeout and retry policy apply to it as well.- Request throttling and GET-response caching are available through
NetworkProviderConfig, both off by default.
Supernova
Block timestamps move from seconds to milliseconds at the Supernova activation epoch without a field rename, and the unit differs per route. Reading such a value as seconds yields a date in the year 57,000.
TransactionOnNetworkexposestimestampMsalongsidetimestamp, plusexecutedAt, which normalises either unit by magnitude.NetworkStatusexposesblockTimestampandblockTimestampMs; block, hyperblock, account and token models expose their millisecond counterparts.- Block models carry
lastExecutionResultHashandlastExecutionResultNonce, which asynchronous execution reports separately from the block itself. miniblockTypeis read with the spelling the node emits.AccountAwaiterpolls every 600 ms, matching sub-second block times.- Added the
reward-revertedstatus.
ABI
- The type names
TokenId,NonZeroBigUint,PaymentandFungiblePaymentresolve. Contracts referencing them carry no definition intypes, so such an ABI failed to load. - Enum variant payload fields are parsed, so fielded enums decode rather than being truncated.
specificTypeand the contract's internal method name are surfaced on the endpoint and parameter models;specificTypedistinguishes au64holding milliseconds from one holding seconds.- Corrected
ExplicitEnumValue.toBytes()and the counted-variadic argument convention. BigFloathas no portable wire form; encoding, decoding andtoBytesthrow. The type exists so that ABIs mentioning it still load.
Removed
Each entry has a working replacement; none of the removed members could produce a valid result.
Removed Use instead SignableMessageMessage+MessageComputer.computeBytesForSigningValidatorSigner(secretKey),ValidatorSigner.fromPemValidatorSigner.custom(signFn)TransactionStatus.recalled,isRecalled— status does not exist on chain NetworkConfig.gasPriceModifierStringgasPriceModifierfunctionCallHexPartson the multisig buildersfunctionCall: <TypedValue>[...]RelayedTransactionsFactory.createRelayedTransactionapplyRelayer, then signcreateTransactionForDelegatingVote— callable only by a contract createTransactionForUnsettingBurnRoleForAllcreateTransactionForUnsettingBurnRoleGloballyTransaction.innerTransactions— not part of the transaction format Public API
NetworkProviderConfig,RetryPolicy,UserAgent,GuardianData,Guardian,CodeMetadata,EsdtTokenPaymentTypeandEgldOrEsdtTokenPaymentTypeare exported. They were declared public but unreachable from the package barrel, which made the configuration surface unusable from outside the package.Tooling
- Generated code is formatted for the language version of the SDK running the generator. The generator wrote unformatted Dart, so any project with a
dart format --set-exit-if-changedcheck failed on its own generated sources. - The ABI validator no longer warns on keys that are not part of the ABI schema, which broke
--fail-on-warningsfor valid ABIs. - Integration tests that perform live network calls are tagged and skipped by default; run them with
dart test -P integration. - Added
dart_styleandpub_semverdependencies.
Generator internals
bin/codegen/utils/imports_formatter.dartdeleted. Import ordering now lives inside each generator via the existingimport_manager/ per-file_writeSortedImportshelpers.BarrelGeneratorno longer dual-tracks event-model file names; the helper computes the suffix once.- Reserved-keyword sanitisation consolidated through
NameSanitizer; the duplicate list inevent_models_generator.darthas been removed (the keyword set is now sourced fromname_sanitizer.dart).
Docs
- New page:
network/entrypoints.mdcovering all eight entrypoint classes plus thegasLimitEstimatorinjection point. - Sidebar moved entrypoints under the existing Network category.
- Codegen and smart-contract pages updated to show the new generator output (
Address/TokenIdentifierwrappers, autogas probe pattern).
Migration
- Move to Dart 3.13 or newer, then
dart pub upgrade abidock_mvx(or bump to^3.0.0inpubspec.yaml). - Regenerate any committed codegen output. Diffs to expect:
- Struct fields that previously held
StringforAddress/TokenIdentifiernow hold the wrapper type. - Generated
*Unsignedcall helpers are unchanged on the wire; the signed helpers now do the probe-then-sign dance internally. - Generated files are formatted.
- Struct fields that previously held
- Walk the call sites; the compiler will flag every
String/Addressmismatch, every removed member listed under Removed, andrelayedVersionmoving fromint?toString?. - Re-check any hard-coded gas limits. Factory-produced limits now include the data-movement term and are higher than before.
- Re-check anything that reads
TransactionOnNetwork.timestampdirectly; preferexecutedAt. - (Optional) Swap
ApiNetworkProvider(...)+ factory plumbing forDevnetEntrypoint()/MainnetEntrypoint()etc.
-
1.2.027 Apr 2026Release notes
Open source →Public-key encryption now actually uses X25519
PubkeyEncryptorandPubkeyDecryptorwere feeding raw Ed25519 key bytes into X25519 APIs. The code round-tripped with itself (both sides made the same mistake), but the ciphertext couldn't be decrypted by anything else — mx-sdk-js-core, NaCl, libsodium, or any tool that does real X25519.This release adds the two standard Curve25519 key conversions, both exposed under
lib/src/wallet/crypto/curve25519_conversion.dart:ed25519PublicKeyToX25519(edPub)— the Bernstein/RFC-7748 birational mapu = (1 + y) / (1 - y) mod (2^255 - 19).ed25519SeedToX25519SecretKey(seed)—SHA-512(seed)[0:32]followed by RFC 7748 clamping, matching libsodium'scrypto_sign_ed25519_sk_to_curve25519.
The encryptor and decryptor now call through these on both sides, so the wire format is real X25519-XSalsa20-Poly1305 and will interoperate with any standard implementation.
Breaking change
Anything encrypted with 1.1.0 or earlier
PubkeyEncryptorcannot be decrypted by 1.2.0, and vice versa. The serialized schema (X25519EncryptedData) is unchanged — only the math behind it is now correct. -
1.1.016 Apr 2026Release notes
Open source →This release closes a pile of wire-format mismatches against the chain, fills in the protocol coverage that was missing (staking, governance, relayed-v3, SC lifecycle), and tightens the concurrency primitives that sit between the SDK and the network. There are a handful of breaking changes, all listed at the bottom — most projects won't notice them.
Wire format
Transaction hashing now matches the chain byte-for-byte. The culprits were subtle: protobuf zero-values were emitted as a single zero byte instead of the sign+magnitude pair,
Option<T>top-level encoding dropped its marker, signedBigIntnested encoding lost its sign extension on-129and friends, and hash-signed transactions were being fed the raw JSON bytes instead of the Keccak digest. The message-signing prefix went back to the canonical\x17Elrond Signed Message:\n— signatures need to interop with existing wallets and hardware devices, so renaming to "MultiversX" wasn't an option.Network provider
Endpoints that were quietly wrong got fixed: bulk-send, VM query, transaction status, NFT nonce padding (even-length hex), process-status vs transaction endpoint on Gateway, the fungible-vs-NFT filter split on
_parseEsdts, account storage key routing, and block-by-nonce shard prefixing. The circuit breaker now wraps smart-contract queries too.ApiNetworkProvider.estimateTransactionCostthrowsUnsupportedErrorinstead of pretending to work.SendTransactionsResultnow returns per-transaction outcomes (SendTxSuccess/SendTxFailurewith the node's rejection reason) alongside the aggregate counts, so bulk submission can drive a resubmission loop properly.New protocol coverage
SmartContractTransactionsFactoryfor deploy / upgrade / change-owner / claim-developer-rewards.StakingTransactionsFactoryfor the direct-staking system SC (stake, unStake, unBond, claim, changeRewardAddress, changeValidatorKeys, unJail, reStakeUnStakedNodes).GovernanceTransactionsFactoryfor proposing, voting, delegate-voting, closing, and claiming accumulated fees.RelayedTransactionsFactoryfor relayed-v3.Transactiongained aninnerTransactionsfield and the protobuf serializer emits them on field 18.TransactionDecoderlearned the matching sealed subclasses:ContractDeploy,ContractUpgrade,ContractChangeOwner,ClaimDeveloperRewards,RelayedV3Transaction.TokenManagementTransactionsFactorygained the nine built-ins it was missing:transferOwnership,controlChanges,ESDTNFTAddURI,stopNFTCreate,transferNFTCreateRole,unsetBurnRoleGlobally,registerAndSetAllRolesDynamic,changeSFTToMetaESDT,updateTokenID.
Concurrency & resilience
CircuitBreakernow enforces a single in-flight probe in the half-open state — the old behaviour let a burst of callers all punch through, which defeated the point.NonceManageruses a proper FIFO Completer-queue mutex and refuses to release nonces that were already committed on-chain.AccountAwaiterandPaginatordedup is keyed correctly now; backoffs cap at the remaining timeout instead of sleeping past it. There's a newRequestThrottletoken-bucket utility for smoothing bursts against rate-limited endpoints (API is ~30 rps per IP).TransactionWatcheraccepts optionalnumShards+roundDuration+awaitCrossShardCompletionflags. When set, it waits for the chain'scompletedTxEvent/SCDeploy/signalErrorlog before returning — no more premature "success" snapshots for cross-shard SC calls.Wallet & accounts
Keystore parsing rejects non-v4 / wrong-cipher / wrong-kdf at load time instead of throwing downstream with a confusing message.
ScryptKeyDerivationParams.permissive()accepts weaker parameters from external wallets but still requiresn >= 1024. Mnemonic derivation and the BIP-39 passphrase are NFKD-normalised now (new dep:unorm_dart) — this matters for non-ASCII passphrases.Account.fromMnemonicdisposes the mnemonic infinally.Signature.fromBytesrejects non-64-byte inputs.Bech32Encoderrejects mixed-case strings per BIP-0173.IAccountpicks upprefersHashSigning,signAsGuardian, andsignAsRelayer— the minimal surface a controller needs to support hardware and remote signers without hard-coding the key material.Codegen
Queries that return a struct, an enum, or a list of them used to crash at runtime because the generated
fromAbipath only handled primitives. They work now, includingList<Struct>via(result.typedValues[i] as ListValue).elements.map(...).toList(). Reserved-word parameter names (new,function,class, etc.) get sanitised. Unused imports are gone.example/cookbook/generated/pairregenerates analyzer-clean.Core types
Addressequality now includes the HRP (two addresses with identical bytes but differenthrpare not equal — they represent different networks), andAddressComputer.computeContractAddresspropagates the deployer's HRP instead of hard-coding"erd".Transaction.dataandMessage.bytesreturn defensive / unmodifiable views so callers can't mutate the source.Balancegained*,~/,%, andratioTo. NumericaltoBytes()methods now return fresh allocations instead of shared mutable singletons.AccountStoragepicked up ESDT key-prefix helpers:esdtEntry,esdtRolesEntry,esdtLastNonceEntry,entriesWithPrefix.Mnemonic-from-keystore
Three new public entry points for integrators that need to recover the mnemonic directly from a
kind == "mnemonic"keystore:UserWallet.loadMnemonic(path, password)— reads a keystore file and returns the decryptedMnemonic.UserWallet.decryptMnemonic(json, password)— same thing, but from an already-parsed JSON map.UserWallet.decryptMnemonicBytes(json, password)— returns the raw UTF-8 bytes of the mnemonic phrase, so external libraries can build their own wrapper without going through theMnemonicclass.
Breaking changes
Address ==/hashCodeinclude the HRP.Signature.fromBytes/.fromUint8Listreject non-64-byte inputs.IAccounthas three new required members:prefersHashSigning,signAsGuardian,signAsRelayer.SendTransactionsResultadds anoutcomesfield.TransactionaddsinnerTransactions.Transaction.dataandMessage.bytesare no longer mutable.TransactionComputer.applyGuardianthrowsStateErrorwhen replacing a different guardian (instead of silently overwriting).
-
1.0.112 Apr 2026Release notes
Open source →Security
- Transaction fee calculation now uses pure integer arithmetic instead of lossy
doublemultiplication, matchingmx-chain-gobehaviour for gas prices exceeding 2^53. ProtoSerializer._serializeValuerejects negativeBigIntvalues at runtime instead of silently producing malformed hex.Address.hashCodeuses FNV-1a with& 0x7FFFFFFFmask, preventing unbounded integer growth on web targets.Balance.fromEgldandManagedDecimalValue.fromDoubleusetoStringAsFixedto eliminate IEEE 754 floating-point noise before parsing.Ed25519Crypto.generatePublicKeynow zeros extracted seed bytes in afinallyblock, matching the pattern already used insign.ListBinaryCodec.decodeTopLevelguards against zero-progress decoding loops that could cause infinite loops / OOM.Address.fromHexvalidates decoded byte length at runtime (not just via debugassert).BooleanBinaryCodecandOptionBinaryCodecmarker buffers now return fresh allocations instead of shared mutable singletons.UserSecretKey.generaterejects all-zeros and all-0xFF seeds fromRandom.secure().
Fixed
SmartContractResult.fromJsonno longer throwsFormatExceptionwhen a network response returns a numericvaluefield (Gateway and some API shapes emitvalue: 0asint).- Generator
models_generator.dartnow emits the enum-discriminant guard as a braced block so regenerated code stays analyzer-clean undercurly_braces_in_flow_control_structures. - Applied the same block-style fix to the committed generated output under
example/cookbook/generated/.
Changed
- Removed the arbitrary max-scale-77 restriction from
ManagedDecimalBinaryCodec.encodeNestedto matchencodeTopLeveland the Rust SDK. - Added
@visibleForTesting BinaryCodec.resetCache()to allow test isolation of the codec singleton. NativeSerializer._toBigIntusesBigInt.from(value)directly for doubles instead of the truncatingBigInt.from(value.toInt()).Transaction.copyWithaccepts an optionalnewDataparameter.Nonce.operator -throwsArgumentErrorat runtime if the result would be negative, instead of relying on a debug-onlyassert.- Added
ScryptKeyDerivationParams.permissive()constructor for keystore import/decryption, accepting weaker KDF parameters from external wallets.EncryptedData.fromJsonnow uses it. Address.getShardOfAddressuses integer bit-scan instead of floating-pointlogfor the shard mask computation.- Codebase-wide
dart formatsweep to satisfy thedart format --set-exit-if-changedCI gate.
- Transaction fee calculation now uses pure integer arithmetic instead of lossy
-
1.0.0-beta.220 Dec 2025 pre-releaseRelease notes
Open source →Fixed
- Fixed dangling library doc comments in codegen files.
- Updated package description to meet pub.dev length requirements.
- Replaced
flutter_lintswithlintsfor pure Dart compatibility.
-
1.0.0-beta.120 Dec 2025 pre-releaseRelease notes
Open source →Added
- First public release of the MultiversX Dart/Flutter SDK and CLI.
- Wallet tooling covering mnemonic, PEM, and keystore workflows.
- Transaction builders for EGLD, ESDT, NFT, SFT, and MetaESDT transfers.
- High-level smart-contract controller with ABI-driven calls, queries and events.
- Gateway and REST network providers plus WebSocket event streams.
- ABI codecs for primitives, collections, composites, and protocol-specific special types.
- Code generator capable of scaffolding controllers, DTOs, and tests from ABI files.
- Cookbook examples and wallet walkthroughs demonstrating real integrations.
- 900+ automated tests spanning core types, infrastructure, serializers, and integration scenarios.