Integrating a market
Discover the engine, read its underlying assets, simulate your call, and reconcile the receipt against actual shares and payouts.
Examples target the recorded Base Sepolia test deployment. Test tokens only; Mintware contracts are unaudited.
Read the engine at one block
Use a single blockNumber for related portfolio reads, so balances, supply, liquidity and marks describe the same state. lib/gateway/marketEngine.ts provides readMarketEngineSnapshot, used by the RWA reader. Treat unavailable NAV as unknown, never as zero.
| Read | Interpretation |
|---|---|
token0(), token1(), quoteAsset(), poolKey(), poolManager() | Underlying input/output order, quote denomination, and the pool identity. For owned RWAs, PoolKey uses the virtual adapter while token0/token1 use underlying. |
activated(), paused(), owner(), guardian() | Engine admission and control state. An active registry row does not replace these checks. |
totalNav(), totalSupply(), balanceOf(account), decimals() | Marked portfolio value in raw quote units, 18-decimal share supply and account ownership. totalNav excludes pending LP fees and may revert. |
adapter0(), adapter1(), positionLiquidity(), tickLower(), tickUpper() | Where assets rest and the current LP range. Read balances on the underlying token contracts and totalAssets on adapters. |
nonQuoteMark(), lifecycleModule() | Installed price source and immutable callback executor. Read latestMark on the mark contract. |
recenterEdgeTicks(), maxRecenterTickMove(), recenterCooldown(), lastRecenterAt() | Public recenter settings. Also read PoolManager spot to assess whether a move is due. |
pausedAt(), maxPauseDuration() | Engine pause timestamp and timeout; zero duration disables permissionless timeout recovery. |
appraisalHook(), permissionsAdapter() | Owned-wrapper-only getters. On the hook: bandStatus(), config(), isFresh(), oracleReady(), tradingPaused(), observedAt(). On the adapter: allowedWrappers(), isAllowed(), swappingEnabled(). |
The shared MARKET_ENGINE_ABI covers app reads and deposit/withdraw. Add source-confirmed view signatures with viem’s parseAbi when you need getters not included there. Internal immutable caps and mark-age limits have no public getters; inspect the deployment’s constructor configuration.
Deposit from code
function deposit(uint256 amount0, uint256 amount1, uint256 minSharesOut)
external returns (uint256 sharesMinted)
function withdraw(uint256 shares, address recipient, uint256 minAmount0, uint256 minAmount1)
external returns (uint256 burned, uint256 amount0Out, uint256 amount1Out)The following snippets run in order within your app, using connected viem clients and the repository’s existing ABI. Use the RwaOwnedPool_Engine deployment key instead for the owned unit, after checking contributor and engine permissions. Its deposit assets are still the underlying engine tokens.
import deployments from '@/config/deployments.json'
import { MARKET_ENGINE_ABI as abi } from '@/lib/web3/artifacts/marketEngine'
import { erc20Abi, getAddress, parseEventLogs } from 'viem'
// publicClient and walletClient are your connected viem clients.
const deployment = deployments.testnet['base-sepolia'].MintwareMarketEngine
const engine = getAddress(deployment.address)
const [account] = await walletClient.getAddresses()
if (!account) throw new Error('Connect a wallet')
if (await publicClient.getChainId() !== deployment.chainId ||
await walletClient.getChainId() !== deployment.chainId) {
throw new Error('Switch both clients to the deployment chain')
}
const [token0, token1] = await Promise.all([
publicClient.readContract({ address: engine, abi, functionName: 'token0' }),
publicClient.readContract({ address: engine, abi, functionName: 'token1' }),
])Read each underlying token’s decimals() before converting a user-entered amount to raw units. Approvals go to the engine. You may contribute zero of one token; both amounts cannot be zero. There is no previewDeposit method on this two-asset engine: use its math and a fresh simulation, allowing for fees collected during the actual deposit.
// All amounts are bigint in token raw units; shares have 18 decimals.
// Choose minSharesOut from your quote and accepted slippage, not zero.
async function deposit(amount0: bigint, amount1: bigint, minSharesOut: bigint) {
for (const [token, amount] of [[token0, amount0], [token1, amount1]] as const) {
if (amount === 0n) continue
const hash = await walletClient.writeContract({
account, chain: walletClient.chain,
address: token, abi: erc20Abi, functionName: 'approve',
args: [engine, amount],
})
const approval = await publicClient.waitForTransactionReceipt({ hash })
if (approval.status !== 'success') throw new Error('Approval reverted')
}
const { request } = await publicClient.simulateContract({
account, address: engine, abi, functionName: 'deposit',
args: [amount0, amount1, minSharesOut],
})
const hash = await walletClient.writeContract(request)
const receipt = await publicClient.waitForTransactionReceipt({ hash })
if (receipt.status !== 'success') throw new Error('Deposit reverted')
return parseEventLogs({ abi, eventName: 'Deposited',
logs: receipt.logs.filter(log => log.address.toLowerCase() === engine.toLowerCase()),
})
}Withdraw from code
A withdrawal burns the caller’s shares; it is not an ERC-4626 redeem on behalf of another owner. Set both minimum token amounts from your accepted payout bounds. Zero is permitted for a withdrawal minimum, but gives up protection for that token. Simulation previews a point in time, not a guarantee at transaction inclusion.
// Redeem your own shares. No share-token approval to the engine is needed.
async function withdraw(shares: bigint, min0: bigint, min1: bigint) {
const { request, result: preview } = await publicClient.simulateContract({
account, address: engine, abi, functionName: 'withdraw',
args: [shares, account, min0, min1],
})
// preview = [burned, amount0Out, amount1Out] at simulation time only.
const hash = await walletClient.writeContract(request)
const receipt = await publicClient.waitForTransactionReceipt({ hash })
if (receipt.status !== 'success') throw new Error('Withdrawal reverted')
const events = parseEventLogs({ abi, eventName: 'Withdrawn',
logs: receipt.logs.filter(log => log.address.toLowerCase() === engine.toLowerCase()),
})
// Use sharesBurned and delivered amounts from events, not requested shares.
return { preview, events }
}Events to reconcile
event Deposited(address indexed account, uint256 amount0, uint256 amount1, uint256 shares)
event Withdrawn(address indexed account, address indexed recipient, uint256 sharesBurned, uint256 amount0, uint256 amount1)| Events | What they tell you |
|---|---|
EngineActivated | The bound adapters and mark at activation. |
IdleAllocated, IdleRecalled, LiquidityDeployed, IdleSwapped | Allocation requests, measured recalls, added liquidity and actual swap inputs/outputs. |
FeesCollected, PositionRecentered | Explicit harvest receipts; old/new ticks and resulting liquidity for recenter. Internal fee collections do not separately emit FeesCollected. |
Transfer, Paused, Unpaused, GuardianSet, MaxPauseDurationSet, AutoUnpaused | Share movement/mint/burn and engine control changes. Owned shares permit mint/burn only. |
ObservationRecorded, AppraisalPosted, KeeperProposed, KeeperSet, ConfigProposed, ConfigApplied, TradingPaused, TradingResumed | Appraisal provenance and hook governance. The observation timestamp and report hash are in ObservationRecorded; publication time is in AppraisalPosted. |
Index by chain, engine address, transaction and log position, with reorganization handling. Decode events from the expected contract address. A recorded operation is not a substitute for current balances or token eligibility.
Public app read routes
These GET routes exist in the app. The gateway path is a compatibility name for market discovery and the engine registry, not the retired LP Gateway product. Addresses and pool identifiers in query examples below are placeholders.
| GET route | Parameters | Response and limitations |
|---|---|---|
/api/rwa/unit | unit=wcp7&account=<wallet> | unit defaults to wcp7; account is optional. One-block engine and hook reads, underlying balances, mark/band and permissions. source is live, cached or undeployed. Missing deployment configuration returns undeployed; failed reads return a previous cached result or 503. Short-lived successful responses are also cached. |
/api/gateway/markets | None | Active, verified registry markets across configured market chains. Includes engine address, chainId, pair label and navQuote; NAV can be null. Cached for 30 seconds. A failed chain registry read can omit that chain’s rows. |
/api/gateway/instances | chainId=<id>&all=1 | Registry records. Defaults to the configured chain and active rows; all=1 includes inactive rows. Returns an empty list if engine configuration is absent. positionManager is a compatibility alias for engineAddress. |
/api/gateway/meta | pool=<pool-id> | Resolves a verified engine and returns token order, decimals, quote, chain and ticks. Optional pm selects an engine generation, including inactive engines for exits. Use the RWA endpoint for owned-pool hook state, not the legacy dynamicFee field here. |
/api/gateway/position | address=<wallet>&pool=<pool-id> | Current shares, supply and marked claim; nullable NAV/valuation. Cost basis replays verified transaction events and is unavailable for non-quote contributions or share mismatches. Optional pm selects a generation; inactive engines remain resolvable for exits. |
/api/gateway/discover | None | Pool watchlist, activity and ranking signals. A discovery result is not an approved deployment; the feed can be cached or empty when its source/configuration is unavailable. |
These are public reads, subject to rate limits and runtime configuration. The RWA endpoint reports its block and source; do not treat a cached snapshot as current eligibility. POST /api/rwa/live-trade and the old buffer write on POST /api/gateway/position are retired with HTTP 410. Signing and broadcasting the examples above happens in your connected wallet.
For each engine address and explorer, see Contracts.
Source reference
Behavior above follows these files in the Mintware repository. Deployment records describe recorded state, not a fresh chain read.
lib/web3/artifacts/marketEngine.tslib/gateway/marketEngine.tslib/rwa/read.tscontracts-v4/src/vaults/MintwareMarketEngineCore.solapp/api/rwa/unit/route.tsapp/api/gateway/markets/route.tsapp/api/gateway/instances/route.tsapp/api/gateway/meta/route.tsapp/api/gateway/position/route.tsapp/api/gateway/discover/route.tsapp/api/rwa/live-trade/route.ts