Skip to main content

Wallet SDK Reference

Complete API reference for @intentguard/sdk. For an integration walkthrough, see the Integration Guide.

Configuration

Constructor

The only required parameter is chainId. All other parameters have defaults:

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

const client = new IntentGuardClient({ chainId: 1 });
ParameterTypeRequiredDefaultDescription
chainIdnumberYesEVM chain ID (e.g. 1 for mainnet, 11155111 for Sepolia)
rpcUrlstringNo"https://rpc.intentguard.xyz"IntentGuard RPC endpoint
enforcerTypeEnforcerTypeNo"balance"Enforcer to use for constraint verification (see Enforcer types)
defaultValidityBlocksnumberNo5Blocks ahead to use as validUntilBlock when not provided. Applies to delegated mode only.
minValidityBlocksnumberNo2Minimum acceptable validUntilBlock - currentBlock. Requests below this threshold are rejected client-side with INVALID_VALIDITY_WINDOW.
timeoutMsnumberNoNo timeoutRequest timeout in milliseconds per RPC call
fetchFetchFnNoGlobal fetchCustom fetch implementation

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

Environment variables

For Node.js or server-side deployments. In browser environments, pass config via the constructor instead.

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

const client = new IntentGuardClient(fromEnv());

fromEnv() returns an IntentGuardConfig object. It throws if INTENTGUARD_CHAIN_ID is not set or is not a positive integer.

VariableRequiredDefaultDescription
INTENTGUARD_CHAIN_IDYesChain ID (integer)
INTENTGUARD_RPC_URLNohttps://rpc.intentguard.xyzIntentGuard RPC endpoint
INTENTGUARD_ENFORCER_TYPENobalanceEnforcer type for constraint verification

Types

BalanceTarget

Lightweight account/token pair whose balance is snapshotted before the user's transaction executes.

interface BalanceTarget {
account: string;
token: string;
}

ProtectedConstraint

Enforcement rule verified after execution against pre-transaction snapshots.

interface ProtectedConstraint {
account: string;
token: string;
maxOutflow: string;
minInflow: string;
}

All amount fields are decimal strings in the token's base units (amount × 10^decimals).

EthereumSigner

Signer interface compatible with ethers.js v6 Signer and viem WalletClient adapters.

interface EthereumSigner {
signTypedData(
domain: TypedDataDomain,
types: Record<string, TypedDataField[]>,
value: Record<string, unknown>,
): Promise<string>;
}

UnsignedEnforcementTx

Returned by prepareSelfSignedProtection, buildPreSelfSignedTx, and buildPostSelfSignedTx.

interface UnsignedEnforcementTx {
to: string;
data: string;
value: "0";
}

SelfSignedTxPayload

Input to submitSelfSignedTransaction.

interface SelfSignedTxPayload {
preTx: string;
userTx: string;
postTx: string;
retryUntilBlock: number;
}

Constants

ConstantValueDescription
NATIVE_ETH"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"Sentinel address for native ETH (EIP-7528)
DEFAULT_VALIDITY_BLOCKS5Default validUntilBlock offset
DEFAULT_MIN_VALIDITY_BLOCKS2Minimum acceptable validity window
DEFAULT_RPC_URL"https://rpc.intentguard.xyz"Default RPC endpoint
DEFAULT_ENFORCER_TYPE"balance"Default enforcer type
PRE_SELF_SIGNED_SELECTOR4-byte selectorpreSelfSigned function selector
POST_SELF_SIGNED_SELECTOR4-byte selectorpostSelfSigned function selector

Self-signed mode API

prepareSelfSignedProtection

Build both unsigned enforcement transactions in a single call.

const { preTx, postTx } = await client.prepareSelfSignedProtection(
targets, // BalanceTarget[]
constraints, // ProtectedConstraint[]
validUntilBlock, // number
options?, // { enforcerType? }
);

Returns: { preTx: UnsignedEnforcementTx, postTx: UnsignedEnforcementTx }

buildPreSelfSignedTx

Build the unsigned pre-enforcement transaction only.

const preTx = await client.buildPreSelfSignedTx(
targets, // BalanceTarget[]
validUntilBlock, // number
options?, // { enforcerType? }
);

Returns: UnsignedEnforcementTx

buildPostSelfSignedTx

Build the unsigned post-enforcement transaction only.

const postTx = await client.buildPostSelfSignedTx(
constraints, // ProtectedConstraint[]
options?, // { enforcerType? }
);

Returns: UnsignedEnforcementTx

submitSelfSignedTransaction

Submit a self-signed enforcement bundle.

const txHash = await client.submitSelfSignedTransaction({
preTx: signedPreTx, // 0x-prefixed signed raw transaction
userTx: signedUserTx, // 0x-prefixed signed raw transaction
postTx: signedPostTx, // 0x-prefixed signed raw transaction
retryUntilBlock, // number
});

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

Client-side validation:

  1. All three transaction strings must be non-empty and 0x-prefixed.
  2. All three transactions must be signed by the same address (recovered via ECDSA).
  3. Only EIP-1559 (type 2) transactions are supported.

Low-level calldata encoding

For integrations that build transactions outside the SDK:

import {
encodePreSelfSignedCalldata,
encodePostSelfSignedCalldata,
PRE_SELF_SIGNED_SELECTOR,
POST_SELF_SIGNED_SELECTOR,
} from "@intentguard/sdk";

// preSelfSigned: selector + ABI-encoded (targets[], validUntilBlock)
const preData = encodePreSelfSignedCalldata(targets, validUntilBlock);

// postSelfSigned: selector + ABI-encoded (constraints[])
const postData = encodePostSelfSignedCalldata(constraints);

Address recovery utility

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

const signer = recoverTxSender(signedRawTx); // checksummed address

Delegated mode API

Note: Delegated mode is available on testnet only.

submitProtectedTransaction

Submit a protected transaction with in-process constraint signing.

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?, enforcerType? }
);
ParameterTypeRequiredDefaultDescription
rawTxstringYes0x-prefixed signed raw transaction hex
constraintsProtectedConstraint[]Yes1 to 10 balance constraints to enforce
signerEthereumSignerYesAccount that signed rawTx. Must implement signTypedData() and getAddress(). Must be the same account that signed the raw transaction.
options.validUntilBlocknumberNocurrentBlock + defaultValidityBlocksBlock number after which the request expires.
options.enforcerTypeEnforcerTypeNoClient-level enforcerTypeOverride the enforcer type for this call only.

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

submitProtectedTransactionWithRetry

Convenience wrapper that retries on PROTECTED errors, waiting for the next block between attempts.

const txHash = await client.submitProtectedTransactionWithRetry(
rawTx,
constraints,
signer,
{
retryUntilBlock: currentBlock + 10,
onRetry: ({ attempt, retryAfterBlock, error }) => {
console.log(`Attempt ${attempt}: retrying at block ${retryAfterBlock}`);
},
},
);
ParameterTypeRequiredDefaultDescription
rawTxstringYes0x-prefixed signed raw transaction hex
constraintsProtectedConstraint[]Yes1 to 10 balance constraints to enforce
signerEthereumSignerYesAccount that signed rawTx.
options.retryUntilBlocknumberYesBlock at which retries stop. Also sent as validUntilBlock.
options.enforcerTypeEnforcerTypeNoClient-level enforcerTypeOverride enforcer type for this call.
options.signalAbortSignalNoNo signalAbort signal for cancellation. Throws with code ABORTED.
options.onRetryfunctionNoNo callbackCalled after each PROTECTED error. Receives { attempt, retryAfterBlock, error }.

Returns: Promise<string> — the transaction hash on success.

Throws: RetryExhaustedError when the block window expires. Inspect error.attempts, error.errors (full list), and error.lastError for debugging.

The retry loop stops when:

  • The transaction succeeds (returns the tx hash)
  • A non-PROTECTED error is thrown — rethrown immediately
  • The next block exceeds retryUntilBlock — throws RetryExhaustedError
  • The signal is aborted — throws with code ABORTED

buildConstraintMessage

Produce EIP-712 typed data for external signing. Use when the signer is external (hardware wallet, browser extension, custody provider).

const msg = await client.buildConstraintMessage(
rawTx, // 0x-prefixed signed raw transaction
constraints, // ProtectedConstraint[] (1 to 10 constraints)
userAddress, // address of the transaction sender
options?, // optional: { validUntilBlock?, enforcerType? }
);

Returns:

  • domain — EIP-712 domain (name, version, chainId, verifyingContract)
  • types — EIP-712 type definitions
  • value — EIP-712 message value (user, txHash, validUntilBlock, constraintsHash)
  • primaryType"SignedConstraints"
  • validUntilBlock — the resolved block number (pass this to submitPreSignedTransaction)

submitPreSignedTransaction

Submit with a pre-signed constraint signature (produced externally via buildConstraintMessage).

const txHash = await client.submitPreSignedTransaction(
rawTx, // 0x-prefixed signed raw transaction
constraints, // ProtectedConstraint[] (same as passed to buildConstraintMessage)
constraintSignature, // EIP-712 signature from external signer
{ validUntilBlock }, // must match what was signed
);

Returns: Promise<string> — the transaction hash.

Advanced signing primitives

For low-level access to the signing primitives (custom hashing, direct contract interaction):

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

const client = new IntentGuardClient({ chainId });
const { contractAddress } = await client.getEnforcerConfig();

import { keccak256 } from "viem";
const txHash = keccak256(rawTx as `0x${string}`);

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

// Or sign directly with an in-process signer
const signature = await signConstraints(
signer, userAddress, txHash, validUntilBlock,
constraints, chainId, contractAddress,
);

Ethereum read methods

The IntentGuard RPC exposes standard Ethereum read methods:

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.

Enforcer types

IntentGuard supports pluggable on-chain enforcement logic. Each enforcer type maps to a verification contract resolved automatically by the backend.

TypeDescription
"balance"Verifies token inflows and outflows per account against declared constraints. Default.
// These are equivalent:
const client = new IntentGuardClient({ chainId: 1 });
const client = new IntentGuardClient({ chainId: 1, enforcerType: "balance" });

The enforcer config is fetched once per type and cached for the lifetime of the client. Additional enforcement strategies will be introduced in future releases.

getEnforcerConfig

const config = await client.getEnforcerConfig(enforcerType?);
// config.contractAddress — enforcer contract address

Validation rules

ConditionResult
validUntilBlock <= current_blockRejected with REQUEST_EXPIRED
validUntilBlock - current_block < 2Rejected with REQUEST_EXPIRED: insufficient time for inclusion
validUntilBlock - current_block > 1000Rejected with VALIDITY_WINDOW_EXCEEDED
All conditions passAccepted

Validity window cap

IntentGuard enforces an upper bound on how far ahead validUntilBlock can be set. The default cap is 1000 blocks (~3.3 hours on mainnet at 12s blocks). Requests exceeding this limit are rejected with VALIDITY_WINDOW_EXCEEDED.

This cap may be adjusted for use cases that require longer validity windows. Contact IntentGuard to discuss higher caps for your deployment.

JSON-RPC reference

The SDK calls intentguard_sendProtectedTransaction over JSON-RPC. The endpoint supports two modes selected by the mode field in the params object.

Self-signed mode (mode: "self-signed"):

{
"jsonrpc": "2.0",
"method": "intentguard_sendProtectedTransaction",
"params": [{
"mode": "self-signed",
"chainId": 11155111,
"preTx": "0x<signedPreTx>",
"userTx": "0x<signedUserTx>",
"postTx": "0x<signedPostTx>",
"retryUntilBlock": 12345678
}],
"id": 1
}

Delegated mode (mode: "managed", testnet only):

{
"jsonrpc": "2.0",
"method": "intentguard_sendProtectedTransaction",
"params": [{
"mode": "managed",
"chainId": 11155111,
"enforcerType": "balance",
"rawTx": "0x<signedRawTx>",
"validUntilBlock": 12345678,
"constraints": [
{
"account": "0x...",
"token": "0x...",
"maxOutflow": "1000000000",
"minInflow": "0"
}
],
"constraintSignature": "0x<eip712Signature>"
}],
"id": 1
}

Both modes return a JSON-RPC response with the user's transaction hash as the result.

EIP-712 domain

FieldValue
name"IntentGuard"
version"1"
chainIdNetwork chain ID
verifyingContractResolved from enforcer type via getEnforcerConfig()

Error reference

All errors extend IntentGuardError with a stable code property.

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

Error classes

ClassDescription
IntentGuardErrorBase class for all SDK errors
InvalidConstraintsErrorConstraint validation failed
InvalidValidityWindowErrorValidity window check failed
IntentGuardNetworkErrorNetwork/RPC communication failure
RetryExhaustedErrorRetry loop exhausted (has attempts, errors, lastError)
SelfSignedValidationErrorSelf-signed bundle validation failed

Exhaustive error handling

try {
const txHash = await client.submitSelfSignedTransaction(bundle);
} catch (err) {
if (!isIntentGuardError(err)) throw err;

switch (err.code) {
// ── Client-side ────────────────────────────────────────────
case IntentGuardErrorCode.INVALID_CONSTRAINTS:
case IntentGuardErrorCode.INVALID_VALIDITY_WINDOW:
break;

// ── Transaction layer ──────────────────────────────────────
case IntentGuardErrorCode.INVALID_TRANSACTION:
case IntentGuardErrorCode.CONSTRAINTS_VALIDATION_ERROR:
case IntentGuardErrorCode.INVALID_USER_TX:
case IntentGuardErrorCode.NONCE_TOO_HIGH:
break;

// ── Constraint layer ───────────────────────────────────────
case IntentGuardErrorCode.CONSTRAINTS_EMPTY:
case IntentGuardErrorCode.CONSTRAINTS_LIMIT_EXCEEDED:
case IntentGuardErrorCode.INVALID_TOKEN_ADDRESS:
case IntentGuardErrorCode.INVALID_ACCOUNT_ADDRESS:
break;

// ── EIP-712 signature layer (delegated mode) ───────────────
case IntentGuardErrorCode.INVALID_CONSTRAINT_SIGNATURE:
case IntentGuardErrorCode.SIGNER_MISMATCH:
case IntentGuardErrorCode.CHAIN_MISMATCH:
case IntentGuardErrorCode.REQUEST_EXPIRED:
case IntentGuardErrorCode.VALIDITY_WINDOW_EXCEEDED:
break;

// ── Simulation layer ───────────────────────────────────────
case IntentGuardErrorCode.PROTECTED:
break;

// ── Submission layer ───────────────────────────────────────
case IntentGuardErrorCode.NETWORK_ERROR:
case IntentGuardErrorCode.SUBMISSION_FAILED:
case IntentGuardErrorCode.RPC_ERROR:
case IntentGuardErrorCode.SERVICE_UNAVAILABLE:
break;

// ── Configuration layer ────────────────────────────────────
case IntentGuardErrorCode.UNKNOWN_ENFORCER_TYPE:
break;

// ── Self-signed layer ──────────────────────────────────────
case IntentGuardErrorCode.MISSING_SIGNED_TX:
case IntentGuardErrorCode.SIGNER_MISMATCH_SELF_SIGNED:
case IntentGuardErrorCode.INVALID_TX_ORDERING:
case IntentGuardErrorCode.INVALID_NONCE_SEQUENCE:
case IntentGuardErrorCode.WRONG_ENFORCEMENT_CONTRACT:
break;

// ── Client-side control flow ───────────────────────────────
case IntentGuardErrorCode.TIMEOUT:
case IntentGuardErrorCode.ABORTED:
case IntentGuardErrorCode.RETRY_EXHAUSTED:
break;

default:
throw err;
}
}

All error codes

CodeLayerRetryableDescription
Client-side
INVALID_CONSTRAINTSClientNoInvalid addresses, negative amounts, or exceeds the 1–10 limit.
INVALID_VALIDITY_WINDOWClientNovalidUntilBlock is in the past or too close to the current block.
NETWORK_ERRORClientYesRPC endpoint unreachable.
TIMEOUTClientNoOperation timed out.
ABORTEDClientNoOperation was cancelled via AbortSignal.
RETRY_EXHAUSTEDClientNoRetry loop exhausted all attempts.
Transaction layer
INVALID_TRANSACTIONServerNoRaw transaction is malformed, empty, or cannot be decoded.
CONSTRAINTS_VALIDATION_ERRORServerNoConstraint verification failed (generic).
INVALID_USER_TXServerNoThe user-submitted transaction is invalid.
NONCE_TOO_HIGHServerNoTransaction nonce too high; pending transactions must confirm first.
Constraint layer
CONSTRAINTS_EMPTYServerNoNo constraints provided.
CONSTRAINTS_LIMIT_EXCEEDEDServerNoMore than 10 constraints provided.
INVALID_TOKEN_ADDRESSServerNoInvalid token address in a constraint.
INVALID_ACCOUNT_ADDRESSServerNoInvalid account address in a constraint (e.g. zero address).
EIP-712 signature layer (delegated mode)
INVALID_CONSTRAINT_SIGNATUREServerNoEIP-712 constraint signature is malformed.
SIGNER_MISMATCHServerNoRecovered EIP-712 signer does not match the transaction sender.
CHAIN_MISMATCHServerNoChain ID in the EIP-712 domain does not match the transaction chain ID.
REQUEST_EXPIREDServerNovalidUntilBlock has been reached.
VALIDITY_WINDOW_EXCEEDEDServerNovalidUntilBlock is too far ahead. See Validity window cap.
Simulation layer
PROTECTEDServerYes (state-dependent)Constraints were violated. Not included on-chain. May succeed next block.
Submission layer
SUBMISSION_FAILEDServerYesTransaction could not be submitted to the network.
RPC_ERRORServerYesUpstream RPC endpoint returned an error.
SERVICE_UNAVAILABLEServerYesService temporarily unavailable. Retry with backoff.
Configuration layer
UNKNOWN_ENFORCER_TYPEServerNoUnknown enforcer type. Check supportedTypes in the error details.
Self-signed layer
MISSING_SIGNED_TXClient/ServerNoA required signed transaction (preTx, userTx, or postTx) is missing.
SIGNER_MISMATCH_SELF_SIGNEDClientNoThe three self-signed transactions are not signed by the same address.
INVALID_TX_ORDERINGServerNoNonce ordering is incorrect (must be sequential: N, N+1, N+2).
INVALID_NONCE_SEQUENCEServerNoNonces are not consecutive or do not match expected sequence.
WRONG_ENFORCEMENT_CONTRACTServerNoPre or post transaction does not target the expected enforcement contract.

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

Operational notes

  • Set timeoutMs for deterministic request timeouts. If omitted, OS and runtime defaults apply.
  • The SDK enforces a minimum validity window of 2 blocks by default (DEFAULT_MIN_VALIDITY_BLOCKS). This constant is exported from @intentguard/sdk and can be overridden via minValidityBlocks in the constructor config.
  • The fetch option can be used to route traffic through proxies, add mTLS, or inject observability middleware.
  • Supported runtimes: Node.js 22+ or a browser environment with native fetch support. The SDK uses ES2022+ features and does not support Node.js 18 or 20.