Skip to main content

Wallet SDK Integration

IntentGuard enforces deterministic constraints on the outcome of EVM transactions, protecting users against advanced phishing attacks, MEV, and price drift. If any declared constraint is violated, the transaction is rejected pre-inclusion and never executed. No gas is consumed.

Overview

The SDK submits a protected transaction via the intentguard_sendProtectedTransaction JSON-RPC method. Constraints are validated against the transaction before inclusion. If any constraint is violated, the transaction is dropped and never included in a block.

The return value is a transaction hash, identical to a standard eth_sendRawTransaction response.

Installation

npm install @intentguard/sdk

No peer dependencies required.

Configuration

Constructor

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

const client = new IntentGuardClient({
rpcUrl: "https://rpc.intentguard.xyz",
chainId: 1,
contractAddress: "0xDeployedContractAddress",
});
ParameterTypeRequiredDescription
rpcUrlstringNoIntentGuard RPC endpoint. Default: "https://rpc.intentguard.xyz"
chainIdnumberYesEVM chain ID (e.g. 1 for mainnet, 11155111 for Sepolia)
contractAddressstringYesDeployed IntentGuardBalanceEnforcement contract address
timeoutMsnumberNoRequest timeout in milliseconds applied to each RPC call. No timeout by default.
fetchFetchFnNoCustom fetch implementation. Defaults to global fetch.

All JSON-RPC calls are sent to rpcUrl. The SDK includes an X-IntentGuard-SDK header with the package version on every request.

Environment variables

import { IntentGuardClient, fromEnv } from "@intentguard/sdk";

const client = new IntentGuardClient(fromEnv());
VariableRequiredDescription
INTENTGUARD_RPC_URLNoIntentGuard RPC endpoint. Default: "https://rpc.intentguard.xyz"
INTENTGUARD_CHAIN_IDYesChain ID (integer)
INTENTGUARD_CONTRACT_ADDRESSYesIntentGuardBalanceEnforcement contract address

API reference

submitProtectedTransaction

const txHash = await client.submitProtectedTransaction(
rawTx, // 0x-prefixed signed raw transaction
constraints, // ProtectedConstraint[] — 1 to 10 constraints
signer, // EthereumSigner with signTypedData() and getAddress()
options?, // optional: { validUntilBlock?: number }
);
ParameterTypeDescription
rawTxstring0x-prefixed signed raw transaction hex
constraintsProtectedConstraint[]1–10 balance constraints to enforce
signerEthereumSignerWallet that signed rawTx. Must implement signTypedData() and getAddress(). Must be the same account that signed the raw transaction.
options.validUntilBlocknumber (optional)Block number after which the request expires. If omitted, the SDK fetches the current block and adds 10. If provided, the SDK validates that validUntilBlock - currentBlock >= 5. Requests below this threshold are rejected client-side.

Returns: Promise<string> — the transaction hash (0x-prefixed).

Ethereum read methods

Standard Ethereum JSON-RPC read methods routed through the configured RPC endpoint:

const blockNumber = await client.getBlockNumber();
const balance = await client.getBalance("0xAddress");
const nonce = await client.getTransactionCount("0xAddress");
const gasPrice = await client.getGasPrice();
const receipt = await client.getTransactionReceipt("0xTxHash");

Available: getBlockNumber, getChainId, getBalance, getTransactionCount, getCode, getGasPrice, getMaxPriorityFeePerGas, getFeeHistory, estimateGas, call, getBlockByNumber, getBlockByHash, getSyncing, getTransactionByHash, getTransactionReceipt, getLogs.

Constraint reference

Each constraint declares an enforcement rule for a specific account and asset:

interface ProtectedConstraint {
/** Ethereum address whose balance is tracked */
account: string;

/** Asset contract address */
token: string;

/**
* Maximum amount that may leave the account.
* Decimal string in base units (amount * 10^decimals).
* Use "0" to block all outflows.
*/
maxOutflow: string;

/**
* Minimum amount that must arrive at the account.
* Decimal string in base units (amount * 10^decimals).
* Use "0" if no minimum inflow is required.
*/
minInflow: string;
}

1–10 constraints per request. Validation is performed client-side before any network call.

Block unauthorized outflows

Set maxOutflow: "0" to enforce that no amount of the specified asset leaves the account. If any outflow occurs, the transaction is rejected pre-inclusion and not executed.

{
account: "0xCustodyWallet",
token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
maxOutflow: "0",
minInflow: "0",
}

Cap outflows

Set maxOutflow to the expected amount. If the transaction violates declared constraints, it is rejected pre-inclusion and not executed. No gas is consumed.

{
account: "0xUserWallet",
token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
maxOutflow: "500000000", // 500 USDC (6 decimals)
minInflow: "0",
}

Enforce expected outcomes

Use minInflow to require a minimum amount to arrive. Useful for swaps and other state-changing operations.

const constraints = [
{
account: "0xUserWallet",
token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
maxOutflow: "1000000000", // max 1,000 USDC out
minInflow: "0",
},
{
account: "0xUserWallet",
token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
maxOutflow: "0",
minInflow: "500000000000000000", // at least 0.5 WETH in
},
];

Security model

  • Constraint authenticity is verified on-chain via EIP-712 signature verification.
  • Constraint integrity is enforced via deterministic hashing of the constraint set.
  • The constraint signer must match the sender of the raw transaction. Mismatches are rejected.
  • On-chain EIP-712 verification makes constraint enforcement trustless and independently verifiable by any party. Both wallets and users can audit constraint validation on-chain. No trust assumption on IntentGuard infrastructure is required for enforcement correctness.
  • Constraint signatures include a validUntilBlock expiry. Expired requests are rejected before execution.
  • Constraint signatures are domain-separated by chainId and verifyingContract to prevent cross-chain or cross-contract replay.
  • IntentGuard does not custody funds and does not modify transaction semantics beyond constraint enforcement.

Third-party accounts

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

{
account: "0xTreasuryMultisig",
token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
maxOutflow: "0",
minInflow: "0",
}

Error handling

All errors extend IntentGuardError with a stable code property. Use isIntentGuardError for detection and IntentGuardErrorCode for exhaustive switch handling.

import { isIntentGuardError, IntentGuardErrorCode } from "@intentguard/sdk";

try {
const txHash = await client.submitProtectedTransaction(rawTx, constraints, signer);
} catch (err) {
if (!isIntentGuardError(err)) throw err;

switch (err.code) {
case IntentGuardErrorCode.INVALID_CONSTRAINTS:
case IntentGuardErrorCode.INVALID_VALIDITY_WINDOW:
case IntentGuardErrorCode.INVALID_REQUEST:
case IntentGuardErrorCode.INVALID_TRANSACTION:
case IntentGuardErrorCode.PROTECTED:
case IntentGuardErrorCode.NONCE_TOO_HIGH:
// Non-retryable.
break;
case IntentGuardErrorCode.NETWORK_ERROR:
case IntentGuardErrorCode.SUBMISSION_FAILED:
case IntentGuardErrorCode.SERVICE_UNAVAILABLE:
// Retryable with backoff.
break;
default:
throw err;
}
}
CodeOriginRetryableDescription
INVALID_CONSTRAINTSClientNoConstraints contain invalid addresses, negative amounts, or exceed the 1–10 limit.
INVALID_VALIDITY_WINDOWClientNovalidUntilBlock is in the past or too close to the current block (minimum 5 blocks ahead).
NETWORK_ERRORClientYesRPC endpoint unreachable.
INVALID_REQUESTServerNoConstraint signer does not match transaction sender, or request has expired.
INVALID_TRANSACTIONServerNoRaw transaction is malformed, empty, or contains invalid fields.
SUBMISSION_FAILEDServerYesTransaction could not be submitted to the network.
SERVICE_UNAVAILABLEServerYesService temporarily unavailable. Retry with backoff.
PROTECTEDServerNoIntentGuard protection — declared constraints were violated. Transaction was not submitted. No gas consumed.
NONCE_TOO_HIGHServerNoTransaction nonce too high; pending transactions must confirm first.

Passthrough RPC failures surface as RPC_ERROR_<code> (e.g. "RPC_ERROR_-32603").

Low-level signing API

For direct access to EIP-712 typed data (custom UI signing prompts, hardware wallets):

import {
signConstraints,
computeConstraintsHash,
buildConstraintsTypedData,
} from "@intentguard/sdk";

const constraintsHash = computeConstraintsHash(constraints);

const { domain, types, value } = buildConstraintsTypedData(
userAddress,
txHash,
validUntilBlock,
constraintsHash,
chainId,
contractAddress,
);

const signature = await signConstraints(
signer, userAddress, txHash, validUntilBlock,
constraints, chainId, contractAddress,
);

EIP-712 domain

FieldValue
name"IntentGuard"
version"1"
chainIdNetwork chain ID
verifyingContractIntentGuardBalanceEnforcement contract address

Limitations

  • EOA only. IntentGuard requires the transaction sender to be an Externally Owned Account (EOA). Smart Accounts and Account Abstraction (ERC-4337) are currently supported. but will be supported in the next releases.

Operational notes

  • The SDK does not retry requests. Retry logic is the integrator's responsibility.
  • Set timeoutMs to enforce deterministic timeouts. Otherwise, OS/runtime defaults apply.
  • The SDK enforces a minimum validity window of 5 blocks. Requests below this threshold are rejected client-side.
  • Use the fetch config option to route traffic through proxies, add mTLS, or inject observability.
  • Requires Node.js >= 18 or a browser environment with native fetch.

License

MIT