STXN Application SDK — v1.0 (Apps + CallBreaker)
Audience: Teams building next‑gen web3 applications that want to leverage STXN’s clean interfaces, DAG‑based optimal execution, and solver marketplace to reach users at scale.
Scope: How to model calls, create and push UserObjectives via ISmartExecute, optionally integrate IApprover hooks, track lifecycle, and consume outputs.
What’s in v2: No Laminated Proxy/smart-account dependency. Apps push objectives directly to CallBreaker. Execution supports DAG/order control, return-value verification, and a return bus for inter‑call data flow.
0) Quick Start
Install (via Git submodule)
# Add STXN contracts repo as a submodule
git submodule add https://github.com/smart-transaction/stxn-smart-contracts-v2.git lib/stxn
cd lib/stxn && forge build
Minimal push (TypeScript, viem/ethers‑style):
Note: A single CallBreaker is deployed per chain. All applications on that chain interact with the same CallBreaker instance, whether their integration is contract‑based or through a frontend/UI. All pushes are recorded and executed directly on‑chain.
// Note: no npm package available yet. You will need to manually call CallBreaker.
// Example using viem/ethers to build and send a tx directly.
// Build a CallObject (ERC-20 transfer)
const transferCalldata = encodeTransfer(recipient, amount);
const uo = {
appId: toBytes("app.demo.v1"),
nonce: BigInt(Date.now()),
tip: 0n,
chainId: BigInt(8453),
maxFeePerGas: 0n,
maxPriorityFeePerGas: 0n,
sender: userAddress,
callObjects: [
{
salt: 0n,
amount: 0n,
gas: 0n, // 0 lets executor choose; or set a cap
addr: tokenAddress,
callvalue: transferCalldata,
returnvalue: "0x", // fill if you know exact return bytes
skippable: false,
verifiable: true,
exposeReturn: false,
},
],
};
const additional = [];
// Send directly to CallBreaker contract
const tx = await publicClient.writeContract({
address: CALLBREAKER_ADDRESS,
abi: ISmartExecuteABI,
functionName: "pushUserObjective",
args: [uo, additional],
value: 0n,
});
1) Architecture (app-facing)
- Application builds a
UserObjective(one or moreCallObjects) and pushes it to CallBreaker usingISmartExecute.pushUserObjective(can be made gasless for the user; optionalmsg.valueforwarded to pre-approver). - Solvers pick up
UserObjectivePushedevents off-chain, plan execution (DAG/order), and later submitexecuteAndVerifywith user signatures and return data (covered in Solver Guide). - CallBreaker verifies signatures, executes calls in the provided order, checks return values, exposes return data as requested, and settles gas+tips from user balances to the solver.
- (Optional) Your app’s IApprover hooks can enforce policies:
preapproveat push,postapproveafter execution.
2) Core Types & Interfaces
2.1 Solidity data types (push‑time)
// SPDX-License-Identifier: BSL-1.
pragma solidity 0.8.30;
struct CallObject {
uint256 salt; // randomness/idempotency
uint256 amount; // ETH to send
uint256 gas; // per-call gas limit
address addr; // target contract
bytes callvalue; // calldata
bytes returnvalue; // expected return (if verifiable)
bool skippable; // cam be skipped by callbreaker if optimal
bool verifiable; // compare actual vs expected
bool exposeReturn; // expose bytes for later calls
}
struct UserObjective {
bytes appId; // app id for solver selection
uint256 nonce; // anti-replay
uint256 tip; // incentive to solver
uint256 chainId; // target chain
uint256 maxFeePerGas; // caps
uint256 maxPriorityFeePerGas; // caps
address sender; // user address
CallObject[] callObjects; // ordered calls
}
struct AdditionalData { bytes32 key; bytes value; }
interface ISmartExecute {
function pushUserObjective(
UserObjective calldata _userObjective,
AdditionalData[] calldata _additionalData
) external payable returns (bytes32 requestId);
}
Execution‑time: The solver submits a version of
UserObjectivethat includes a user signature. CallBreaker verifies the signature againstgetMessageHash(abi.encode(nonce, sender, abi.encode(callObjects)))duringexecuteAndVerify.
2.2 App hooks
pragma solidity 0.8.30;
import {UserObjective} from "src/interfaces/ISmartExecute.sol";
interface IApprover {
function preapprove(UserObjective calldata _userObjective)
external payable returns (bool);
function postapprove(
UserObjective[] calldata _userObjective,
bytes[] calldata _returnData
) external returns (bool);
}
Register hooks (owner‑only on CallBreaker):
function setApprovalAddresses(bytes calldata appId, address pre, address post) external onlyOwner;
2.3 CallBreaker (app‑relevant surface)
Events
UserObjectivePushed(bytes32 requestId, uint256 sequenceCounter, bytes appId, uint256 chainId, uint256 blockNumber, UserObjective userObjective, AdditionalData[] mevTimeData)Deposit(address sender, uint256 amount)ApprovalAddressesSet(bytes appId, address pre, address post)ValidatorAddressSet(bytes appId, address validator)CallIndicesPopulated()VerifyStxn()
Errors (subset)
PreApprovalFailed(bytes appId),PostApprovalFailed(bytes appId)UnauthorisedSigner(address recovered, address expected)CallFailed(),CallVerificationFailed(),LengthMismatch()OutOfEther(),FlatIndexOutOfBounds(),CallNotFound()
Helpers
- Return bus:
getReturnValue(CallObject),getReturnValueHash(CallObject),hasReturnValue(CallObject),hasZeroLengthReturnValue(CallObject) - DAG utilities:
expectFutureCall(CallObject),expectFutureCallAt(CallObject, index),getCurrentlyExecuting() - Balances:
deposit()andsenderBalances(address)
3) High‑Level Flow (Apps)
- Build a
UserObjectivewith one or moreCallObjects. - Attach
AdditionalData[]if you need metadata (MEV‑time, cross‑chain hints, partner ids, deadlines). - Push with
ISmartExecute.pushUserObjective(uo, additional); you receive arequestId. - Observe
UserObjectivePushedin your indexer. - (Optional) Pre‑approve runs automatically if set for
appId. If it returnsfalse, the push reverts. - Execution is triggered later by solvers (with user signatures).
- (Optional) Post‑approve runs after successful
executeAndVerify.
Gas & Tips: Users fund balances via
deposit(). After execution, CallBreaker charges each usergasUsed × min(maxFeePerGas, basefee + maxPriorityFeePerGas) + tip, transferring to the solver.
4) Building Objectives
4.1 Choosing call boundaries
- Keep
CallObjects focused (one function call each). - Use
skippable=truefor non‑critical steps (e.g.,approveif already sufficient). - Set
verifiable=truewith a knownreturnvaluewhen you can; otherwise leave empty and rely on solver‑providedreturnBytes(still verified). - Set
exposeReturn=trueto make the actual return available to later calls via the return bus.
4.2 DAG & order control
- The solver submits an
orderOfExecutionvector toexecuteAndVerify, enabling parallelizable or dependency‑aware execution. - Utilities
expectFutureCall/expectFutureCallAthelp reason about positions.
4.3 Cross‑chain context
- Use
chainIdinUserObjectiveto target the intended network (can be non‑EVM IDs by convention in your infra). - Put cross‑domain metadata in
AdditionalData[](destination program addresses, partner routing ids, etc.).
5) Hooks: preapprove & postapprove
When to use hooks
- Enforce business policies (KYC tier, daily limits, venue allowlists).
- Ensure funding/tipping constraints before accepting pushes.
- Perform aggregate validations after execution (e.g., expected receipts, accounting logs).
Lifecycle
preapprove(UserObjective)runs insidepushUserObjective(...). It can consumemsg.valuesent by the pusher.postapprove(UserObjective[], bytes[] returnData)runs at the end ofexecuteAndVerify(...).- Register per‑app using
setApprovalAddresses(appId, pre, post).
Keep hooks cheap and deterministic. Heavy checks belong off‑chain.
6) Observability & Indexing
Listen for UserObjectivePushed to ingest work items. Suggested index shape:
{
"requestId": "0x...",
"appId": "0x...",
"chainId": 8453,
"sender": "0x...",
"calls": [ { "addr": "0x...", "selector": "0x...", "salt": "..." } ],
"additional": [ { "key": "0x..", "value": "0x.." } ],
"blockNumber": 12345678,
"pushedAt": 1726789012
}
For post‑execution analytics, consume VerifyStxn() and your own postapprove events.
7) Recipes
7.A One‑click ERC‑20 → ERC‑20 swap
approve(router, amount)—skippable=true.router.swapExactTokensForTokens(...)—verifiable=true. Providereturnvalueif exact bytes known; else leave empty and rely on solver return bytes.- (Optional)
exposeReturn=true, then read withgetReturnValuein a subsequent call (e.g., to route payouts).
7.B Escrow w/ attestation
- Escrow to merchant contract.
- Attestation/Oracle verify —
verifiable=true. - Release — non‑skippable.
7.C Batch payouts
- N
transfercalls. Mark recipients with zero balances asskippable=trueto avoid reverting the whole batch.
7.D Flash‑liquidity / arbitrage (advanced)
- User objective: approvals + target swap.
- Solver objective: provide liquidity, check slippage (
exposeReturn=true), unwind. - Use
orderOfExecutionto interleave user/solver calls safely.
7.E Cross‑chain intent
- Set
chainIdto the destination context and pass target identifiers inAdditionalData[](e.g.,SolanaContractAddress,SolanaWalletAddress).
8) Return Bus & Large Values
- When
exposeReturn=true, CallBreaker stores the bytes in transient storage under the call’s hash key. - Read via
getReturnValue(callObj); when the return is large, only a hash is stored → read viagetReturnValueHash(callObj)and verify off‑chain. - Helpers
hasReturnValue/hasZeroLengthReturnValueaid branching logic.
9) Funding & Settlement
Funding: Users send ETH to deposit(); transfers to the contract through receive/fallback are rejected.
Settlement: After executeAndVerify, CallBreaker computes each user’s cost per UserObjective:
userCost = gasUsed × min(maxFeePerGas, basefee + maxPriorityFeePerGas) + tip
The amount is debited from senderBalances[user] and credited to the solver (the msg.sender of executeAndVerify).
Reverts with OutOfEther() if the user’s balance is insufficient.
10) Security & Best Practices
- Replay safety: Always increment/change
nonceper push. - Verification: Prefer
verifiable=truewith knownreturnvalue. If unknown, supply empty and rely on solver return bytes (still matched). - Determinism: Keep hooks idempotent; a second call should not change outcomes.
- App IDs: Use stable
appIdbyte identifiers; store them server‑side with your product config. - Address hygiene: Treat all user‑provided addresses as untrusted; validate/allow‑list in pre‑approver.
- Testing: Use Foundry helpers to generate signatures and build objectives consistently.
11) Testing & Local Dev
Foundry
forge build
forge test -vvv
forge test --match-test testExecuteAndVerifyWithUserReturns
Signature generation (example)
bytes32 messageHash = callBreaker.getMessageHash(
abi.encode(nonce, sender, abi.encode(callObjects))
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, messageHash);
bytes memory signature = abi.encodePacked(r, s, v);
Integration test skeleton
CallBreaker cb = new CallBreaker(address(this));
vm.prank(user);
cb.deposit{value: 5 ether}();
// build and push
bytes32 req = cb.pushUserObjective(uo, new AdditionalData[](0));
// later: solver submits executeAndVerify(...)
12) Deployment & Configuration
- Register hooks:
setApprovalAddresses(appId, pre, post)(owner‑only). - MEV‑time validator:
setValidatorAddress(appId, validator)(owner‑only). - Deterministic deploys: scripts can deploy with salts (see repo scripts).
- Verification: use Foundry
forge verify-contract/Blockscout tooling per network.
13) Reference: Execution‑time Signature
During executeAndVerify, each UserObjective is verified on‑chain as:
messageHash = getMessageHash( abi.encode( userObj.nonce,
userObj.sender,
abi.encode(userObj.callObjects) ) )
require( ecrecover(messageHash, v, r, s) == userObj.sender )
Ensure your off‑chain signing uses the same ABI encoding for CallObject[].
14) FAQs
Do users need smart accounts? No. EOAs are fine; signatures are verified at execution time.
Is push signed? No. Push is gasless and signature‑free; execution later requires signatures.
Where do tips go? To the solver after successful execution; they’re added to gas cost settlement.
How do we read an earlier call’s output?
Set exposeReturn=true and read via getReturnValue (or hash variant for large values).
Can apps block or rewrite pushes?
preapprove can veto acceptance at push; postapprove can veto after execution (use with care).
15) Change Log
- v1.0 — End‑to‑end app integration guide aligned with v2 contracts:
ISmartExecute,IApprover,CallBreakerDAG/return‑bus model, funding/settlement, hooks, and testing.