Skip to content

senderChecks, recipientChecks, and initiatorChecks: require or forbid EVM contracts and liquidity pools for each party of a transfer.

Address checks constrain the type of address on each side of a transfer. They are how a collection keeps tokens out of pools, or requires that only contracts initiate a flow.

Shape

A complete approvalCriteria with the recipientChecks and initiatorChecks fields open. Folded lines are defaults.

json
{
  "approvalAmounts": {
    "resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
  },
  "maxNumTransfers": {
    "resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
  },
  "overridesFromOutgoingApprovals": true,
  "recipientChecks": {
    "mustBeEvmContract": true,
    "mustNotBeLiquidityPool": true
  },
  "initiatorChecks": {
    "mustNotBeEvmContract": true
  },
  "userApprovalSettings": {
    "userRoyalties": { "percentage": "0", "payoutAddress": "" }
  }
}
{
  "merkleChallenges": [],
  "predeterminedBalances": {
    "manualBalances": [],
    "incrementedBalances": {
      "startBalances": [],
      "incrementTokenIdsBy": "0",
      "incrementOwnershipTimesBy": "0",
      "durationFromTimestamp": "0",
      "allowOverrideTimestamp": false,
      "recurringOwnershipTimes": {
        "startTime": "0",
        "intervalLength": "0",
        "chargePeriodLength": "0"
      },
      "allowOverrideWithAnyValidToken": false,
      "allowAmountScaling": false,
      "maxScalingMultiplier": "0"
    },
    "orderCalculationMethod": {
      "useOverallNumTransfers": false,
      "usePerToAddressNumTransfers": false,
      "usePerFromAddressNumTransfers": false,
      "usePerInitiatedByAddressNumTransfers": false,
      "useMerkleChallengeLeafIndex": false,
      "challengeTrackerId": ""
    }
  },
  "approvalAmounts": {
    "overallApprovalAmount": "0",
    "perToAddressApprovalAmount": "0",
    "perFromAddressApprovalAmount": "0",
    "perInitiatedByAddressApprovalAmount": "0",
    "amountTrackerId": "",
    "resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
  },
  "maxNumTransfers": {
    "overallMaxNumTransfers": "0",
    "perToAddressMaxNumTransfers": "0",
    "perFromAddressMaxNumTransfers": "0",
    "perInitiatedByAddressMaxNumTransfers": "0",
    "amountTrackerId": "",
    "resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
  },
  "coinTransfers": [],
  "requireToEqualsInitiatedBy": false,
  "requireFromEqualsInitiatedBy": false,
  "requireToDoesNotEqualInitiatedBy": false,
  "requireFromDoesNotEqualInitiatedBy": false,
  "overridesFromOutgoingApprovals": true,
  "overridesToIncomingApprovals": false,
  "autoDeletionOptions": {
    "afterOneUse": false,
    "afterOverallMaxNumTransfers": false,
    "allowCounterpartyPurge": false,
    "allowPurgeIfExpired": false
  },
  "mustOwnTokens": [],
  "dynamicStoreChallenges": [],
  "ethSignatureChallenges": [],
  "senderChecks": {
    "mustBeEvmContract": false,
    "mustNotBeEvmContract": false,
    "mustBeLiquidityPool": false,
    "mustNotBeLiquidityPool": false
  },
  "recipientChecks": {
    "mustBeEvmContract": true,
    "mustNotBeEvmContract": false,
    "mustBeLiquidityPool": false,
    "mustNotBeLiquidityPool": true
  },
  "initiatorChecks": {
    "mustBeEvmContract": false,
    "mustNotBeEvmContract": true,
    "mustBeLiquidityPool": false,
    "mustNotBeLiquidityPool": false
  },
  "altTimeChecks": {
    "offlineHours": [],
    "offlineDays": [],
    "offlineMonths": [],
    "offlineDaysOfMonth": [],
    "offlineWeeksOfYear": [],
    "timezoneOffsetMinutes": "0",
    "timezoneOffsetNegative": false
  },
  "mustPrioritize": false,
  "votingChallenges": [],
  "allowBackedMinting": false,
  "allowSpecialWrapping": false,
  "evmQueryChallenges": [],
  "userApprovalSettings": {
    "allowedDenoms": [],
    "disableUserCoinTransfers": false,
    "userRoyalties": { "percentage": "0", "payoutAddress": "" }
  }
}
ts
interface AddressChecks {
  mustBeEvmContract?: boolean;
  mustNotBeEvmContract?: boolean;
  mustBeLiquidityPool?: boolean;
  mustNotBeLiquidityPool?: boolean;
}
FieldTypeDescription
mustBeEvmContractboolThe address must have EVM code
mustNotBeEvmContractboolThe address must not have EVM code
mustBeLiquidityPoolboolThe address must be an x/gamm pool
mustNotBeLiquidityPoolboolThe address must not be an x/gamm pool

The checks attach to a party:

CriterionParty checkedCollectionOutgoingIncoming
senderChecksfromyesnoyes
recipientCheckstoyesyesno
initiatorChecksinitiatedByyesyesyes

An outgoing approval cannot check the sender and an incoming approval cannot check the recipient, because that party is the approval's owner.

Ask your agent:

text
Add a transfer approval to collection 1 where only EVM contracts can receive tokens, and never let tokens move into a liquidity pool.

The MCP builder tools (add_approval) produce the objects on this page.

How It Works

The chain converts the bb1 address to its 20-byte EVM form and asks the EVM module whether code exists there. For pools it looks the address up in the pool address cache that x/gamm fills at pool creation. If the EVM or gamm module is not wired in, the corresponding check answers false.

Checks run after the address lists match. An address must be in fromListId, toListId, or initiatedByListId first, then pass its checks. Several flags on one party combine with AND.

Examples

Only contracts can receive:

json
{
  "recipientChecks": {
    "mustBeEvmContract": true,
    "mustNotBeEvmContract": false,
    "mustBeLiquidityPool": false,
    "mustNotBeLiquidityPool": false
  }
}

Pools cannot send (collection approval):

json
{
  "senderChecks": {
    "mustBeEvmContract": false,
    "mustNotBeEvmContract": false,
    "mustBeLiquidityPool": false,
    "mustNotBeLiquidityPool": true
  }
}

Only contracts can initiate (incoming approval):

json
{
  "initiatorChecks": {
    "mustBeEvmContract": true,
    "mustNotBeEvmContract": false,
    "mustBeLiquidityPool": false,
    "mustNotBeLiquidityPool": false
  }
}

Never send to a pool (outgoing approval):

json
{
  "recipientChecks": {
    "mustBeEvmContract": false,
    "mustNotBeEvmContract": false,
    "mustBeLiquidityPool": false,
    "mustNotBeLiquidityPool": true
  }
}

Uses: contract-only integrations, keeping a token out of liquidity pools, requiring that a human (non-contract) initiates, and protocol-specific routing rules. Pair mustNotBeLiquidityPool with the reserved-address protection described on Overrides when you use forceful transfers.

Edit this page on GitHub