Skip to main content

Wallet SDK Integration Guide

IntentGuard lets wallets attach enforceable outcome constraints to user transactions before private submission. The wallet guarantees properties such as maximum outflow, minimum received amount, or recipient delivery at execution time, not just at simulation time.

If the onchain result violates any declared constraint, the protected bundle is not included onchain and no gas is consumed.

For a conceptual overview of Transaction Outcome Enforcement, see What is IntentGuard?.

How it works

IntentGuard turns a transaction into a conditional execution bundle. Instead of executing unconditionally, the transaction executes only if the resulting state satisfies the user-approved constraints.

Wallet

IntentGuard SDK

Protected bundle:
1. Pre-enforcement tx → snapshots account balances
2. User transaction → executes the intended operation
3. Post-enforcement tx → verifies constraints against snapshots

IntentGuard RPC

Bundle simulation

Private block builders

Included only if constraints pass

Enforcement checks the net balance change of accounts before and after execution. If simulation confirms constraints will pass, the protected bundle is submitted for inclusion. If any constraint is violated, the entire bundle reverts — no partial state changes occur.

Why three transactions?

IntentGuard enforces constraints by comparing account balances before and after execution. The pre-transaction records balance snapshots, and the post-transaction verifies that the resulting state satisfies the user-approved constraints. All three execute atomically as a single protected bundle.

Private submission

Protected transactions must remain private until block inclusion. They must only be submitted through the IntentGuard RPC endpoint.

Do not broadcast protected transactions to public RPC endpoints or the public mempool. Public mempools do not execute enforcement checks, meaning the user transaction could be included without its protection bundle.

The SDK automatically routes protected bundles through the IntentGuard RPC. If you are building a direct integration, ensure your submission path goes exclusively through the IntentGuard RPC.

Wallet integration flow

For wallet teams, this is the expected integration lifecycle:

  1. Wallet receives a transaction from the dApp.
  2. Wallet computes constraints based on the expected outcome (token amounts, slippage tolerance, recipient delivery).
  3. User reviews and approves the transaction and its constraints in the wallet UI.
  4. Wallet submits the protected transaction via the SDK.
  5. SDK handles enforcement — builds the pre/post transactions, manages nonces, signs, and submits the protected bundle.
  6. Wallet receives the transaction hash and can poll for the receipt.

The wallet typically requests a single user approval and signs the three bundle transactions internally.

The wallet's responsibilities are: constraint derivation, user approval UX, and submission. The SDK handles the enforcement mechanics.

What the user should approve

For a protected transaction, the wallet should present both the underlying transaction intent and the enforcement constraints. This is the user's confirmation that the outcome guarantees match their expectations.

Example review screen content:

ActionSwap 1 ETH for USDC via Uniswap
Max ETH spent1.0 ETH
Min USDC received2,500 USDC
Submission routePrivate protected path
Protection expires~2 minutes (10 blocks)

If the swap settles outside these bounds for any reason — including MEV, slippage, or sandwich attacks — the entire bundle reverts and nothing is executed.

Use self-signed mode with the high-level SDK method. This is the simplest and most secure production path.

npm install @intentguard/sdk
import { IntentGuardClient, NATIVE_ETH } from "@intentguard/sdk";

const client = new IntentGuardClient({ chainId: 1 });

const result = await client.protectAndSend({
account: wallet,
transaction: {
to: UNISWAP_ROUTER,
data: swapCalldata,
value: parseEther("1"),
},
constraints: [
{ account: wallet.address, token: NATIVE_ETH, maxOutflow: parseEther("1") },
{ account: wallet.address, token: USDC, minInflow: 2_500_000_000n },
],
waitForReceipt: true,
});

The SDK constructs the enforcement transactions and asks the wallet to sign them. It handles:

  • Building the pre-check and post-check enforcement transactions
  • Inferring balance snapshot targets from constraints
  • Fetching the current nonce and gas parameters
  • Requesting the wallet to sign all three transactions with sequential nonces
  • Submitting the protected bundle through the IntentGuard RPC
  • Optionally waiting for the receipt

Integrations must have access to raw transaction signing. Wallets that expose only sendTransaction without a raw signing path cannot assemble protected bundles.

The account field accepts any raw-sign-capable local wallet directly (e.g. ethers.js v6 Wallet). For viem or custom signing backends, use the fromViemAccount adapter. See the SDK Reference for details.

For custom signing pipelines, direct nonce management, or advanced bundling control, see the Self-signed mode API in the SDK Reference.

Constraint design

Constraints describe the expected economic outcome of a transaction. They define bounds on token flows that must hold after execution.

Each constraint declares bounds on inflows and outflows for a specific account and asset. The SDK accepts bigint or string for amount fields.

Deriving constraints from wallet context

The wallet computes constraints based on the transaction type and the user's expectations:

Swap protection

  • maxOutflow = input token amount (from the swap parameters)
  • minInflow = quoted output amount, adjusted for the user's slippage tolerance
constraints: [
{ account: user, token: inputToken, maxOutflow: inputAmount },
{ account: user, token: outputToken, minInflow: quotedOutput * (1 - slippage) },
]

Transfer protection

  • Sender maxOutflow = transfer amount
  • Recipient minInflow = transfer amount (guarantees delivery)
constraints: [
{ account: sender, token, maxOutflow: amount },
{ account: recipient, token, minInflow: amount },
]

Drain protection

  • Block all outflows on sensitive assets
constraints: [
{ account: user, token: USDC, maxOutflow: 0 },
{ account: user, token: WETH, maxOutflow: 0 },
]

Approval protection

  • Prevent a transaction from moving tokens even if approvals exist
  • Useful when interacting with untrusted contracts
{ account: user, token: USDC, maxOutflow: 0 }

If the contract attempts to transfer USDC via an existing approval, the constraint blocks it.

Native ETH

Use NATIVE_ETH to constrain native ETH (not WETH). The onchain enforcer reads address.balance directly instead of calling balanceOf.

import { NATIVE_ETH } from "@intentguard/sdk";

{ account: user, token: NATIVE_ETH, maxOutflow: parseEther("1") }

NATIVE_ETH is the address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE (EIP-7528 convention).

Third-party accounts

Constraints are not limited to the transaction sender. Any account can be monitored:

{ account: recipientAddress, token: USDC, minInflow: transferAmount }

Limits

1 to 10 constraints per transaction. maxOutflow and minInflow default to 0 when omitted. Validation happens client-side before any network call.

Integration models

IntentGuard supports multiple integration approaches depending on who controls transaction signing and who pays for enforcement transactions.

ModelWho signs enforcementWho pays gasBest for
Self-signedUser walletUserProduction wallets
SponsoredWallet / infrastructureSponsorRetail UX (optional)
DelegatedBackend bundlerBackendTestnet prototyping

Self-signed mode is the baseline trustless architecture. The user signs all three bundle transactions. No backend signing, no trust assumptions.

Sponsored protection is possible when wallets or infrastructure providers choose to pay the enforcement transaction gas on the user's behalf, similar to Account Abstraction paymasters. The signing model remains self-signed.

Delegated mode is available on testnet only. The backend signs enforcement transactions on the user's behalf. See the SDK Reference for the delegated-mode API.

Supported chains

NetworkChain IDSelf-signedDelegated
Ethereum Sepolia11155111AvailableAvailable
Ethereum Mainnet1AvailableNot available

Handling PROTECTED errors

A PROTECTED error means constraints would be violated at the current chain state. The transaction was not included onchain and no gas was consumed.

Typical causes:

  • Price moved between signing and inclusion
  • Liquidity changed
  • Another transaction modified the same balances in the same block

This does not mean the transaction is permanently invalid:

  • No re-signing needed. The same signed bundle can be resubmitted.
  • Wait for the next block before resubmitting. Retrying within the same block produces the same result.
  • The bundle remains valid until its expiry window (ttlBlocks).

For the full error taxonomy, see the SDK Reference.

Security model

Atomic enforcement

Enforcement checks the net balance change of accounts before and after execution. The pre-transaction, user transaction, and post-transaction execute as a single atomic bundle. If any constraint fails during post-verification, the entire bundle reverts — no partial state changes occur.

Signer consistency

All transactions in a self-signed bundle must be signed by the same account. The SDK validates this client-side via ECDSA recovery before submission. The backend performs the same check server-side.

Integrity and replay protection

  • Constraint integrity is enforced via deterministic hashing.
  • Bundles include a block-based expiry. Expired requests are rejected before execution.
  • Signatures are domain-separated by chainId and verifyingContract to prevent cross-chain or cross-contract replay.
  • IntentGuard does not custody funds and cannot modify transactions or constraints.

Concurrent transactions

IntentGuard evaluates each transaction's constraints independently. Account balances are snapshotted immediately before execution and verified immediately after. Multiple protected transactions from the same account can be submitted concurrently.

If two concurrent transactions draw from the same token balance, each one's constraints are evaluated against the actual balance at its own execution point. Constraints must account for each other's balance impact. For independent operations (different token pairs, different pools), there is no interaction between bundles.

Delegatecall and smart wallet considerations

IntentGuard enforces constraints by comparing account balances before and after execution. It does not inspect the internal execution path. Wallets supporting EIP-7702 or smart wallet architectures must ensure that delegatecall targets within a protected transaction are trusted.

An untrusted delegatecall target could:

  • Grant token approvals that persist beyond the bundle, enabling future drains
  • Route funds through intermediary contracts that obscure the net balance change
  • Chain into further delegatecall operations, extending the trust surface

To preserve enforcement guarantees:

  • Only delegatecall into known, audited implementation contracts
  • Reject open-ended proxy chains
  • Treat the enforcement bundle as a trust boundary

Onchain verifiability

The enforcement contract is fully onchain and auditable. The contract address is exposed through getEnforcerConfig(). No trust assumption on IntentGuard infrastructure is required for enforcement correctness.

Limitations

  • EOA only. Smart Account and Account Abstraction (ERC-4337) support is planned.
  • Delegated mode is testnet only. Production deployments must use self-signed mode.
  • Public mempool bypasses protection. Transactions submitted to standard RPC endpoints execute without enforcement.
  • Balance-based enforcement only. Additional enforcement strategies (storage slot verification, custom checks) are planned.
  • Concurrent balance race. If multiple protected transactions draw from the same token balance, constraints must account for each other's balance impact.

FAQ

For conceptual questions about enforcement guarantees, private routing, and how IntentGuard differs from simulation, see What is IntentGuard?.

Which mode should I use?

Self-signed mode for production. Delegated mode only for testnet experimentation.

Can I submit multiple protected transactions concurrently?

Yes. Each transaction's constraints are evaluated independently against the account's balance at the moment of execution. For transactions drawing from the same token balance, ensure each set of constraints accounts for the other's balance changes.

Can transactions still be sent through a standard RPC?

Yes, but doing so bypasses IntentGuard protection entirely. The transaction executes as a normal Ethereum transaction without constraint enforcement.

What if IntentGuard infrastructure is temporarily unavailable?

The transaction can still be submitted through a standard RPC endpoint. It executes normally without outcome enforcement. IntentGuard is optional protection, not a dependency for transaction execution.

How can constraint verification be audited?

The enforcement contract is onchain. Its address is exposed through getEnforcerConfig() and can be inspected by anyone.

What does PROTECTED mean?

Constraints were violated at the current block's state. The transaction was not included and no gas was consumed. Resubmit after the next block without re-signing. See Handling PROTECTED errors.

Why are there three transactions?

IntentGuard enforces constraints by comparing account balances before and after execution. The pre-transaction snapshots balances, and the post-transaction verifies them against your constraints. The three transactions execute atomically as a single bundle.

Can enforcement transactions be sponsored?

Yes. Wallets or infrastructure providers may choose to pay the enforcement transaction gas on the user's behalf, similar to Account Abstraction paymasters. The signing model remains self-signed.