HinkalHelper
Overview
HinkalHelper acts as the policy and preflight engine for Hinkal transactions. It centralizes:
Relay whitelisting and fee policy (via
RelayStore)Token registry and per-token limits (via
ERC20TokenRegistry)Circuit public input construction (via
CircomDataBuilder)EIP-712 validation for in-logic external call metadata
Access token (allowlist) verification and minting
The helper is upgradeable separately from core state, allowing governance to evolve policy without changing state layout.
Inheritance
RelayStore— whitelisted relays and fee calculationsERC20TokenRegistry— gas token list and per-token limitsCircomDataBuilder— forms public inputs for circuitsHinkalInLogicMetadataParser— decodes signed metadataEIP712— domain separator and typed-data hashing
Key Storage
address public hinkalAddress— only this address may call certain functionsIAccessToken public accessToken— access token registryaddress public inLogicSigner— signer authorized for EXECUTE metadata
Admin Functions
setAccessToken(address)— update access token contractsetHinkalAddress(address)— set the canonical Hinkal callersetInLogicSigner(address)— set expected signer for EXECUTE metadataupdateStakeCircuits(uint256 id, bool value)— toggle stake-circuit IDs inCircomDataBuilderPlus the registry/relay admin functions inherited from parents.
Relay Policy
function relayerIsValid(address relay) internal viewIf relay is non-zero: requires
tx.origin == relayandisRelayInList(relay). Rationale: ensures the relay who submits is the one being paid and is whitelisted.
function checkRelayFeeParameters(CircomData circomData) internal viewFor non-wallet actions or Emporium action,
feeTokenmust be a registered gas token.For any non-zero logic action,
feeTokenmust be present in the tx token list.
Token Registry Policy
function checkTokenRegistry(address[] erc20TokenAddresses, int256[] amountChanges)
public view returns (bool limitHit)If registry disabled, returns false.
Otherwise, checks each token’s absolute
amountChanges[i]against configuredtokenLimit.Returns true if any limit is exceeded — used to decide if access token checks/minting are required.
Dimension and Shape Validation
function dimensionsCheck(CircomData circomData, Dimensions dimensions) internal pureEnforces equality of array lengths with
dimensionsfor tokens, nullifiers per input, and outputs.Requires non-zero
extraRandomizationinstealthAddressStructure.Asserts
onChainCreation,slippageValues, anduseApprovalUtxoDatalengths equal token count.If
hinkalLogicAction != 0, requiresexternalActionId == HINKAL_WALLET_ACTION_ID(wallet flow).If action is not RELEASE_BUFFER/EXECUTE, then
executeApprovalChangesmust be all zeros.
In-Logic Metadata Validation (EXECUTE)
function checkInLogicMetadata(CircomData circomData) internal viewOnly for action EXECUTE (3):
Parses metadata, checks EIP-1271/EOA signature with
inLogicSignerover a typed hash of(deadline, tokens[], externalAddress, externalCallData).Requires
deadline >= block.timestamp.Ensures every signed token is present in
circomData.erc20TokenAddresses.
function calculateInLogicHash(CircomData circomData, ParsedInLogicMetadata parsed)
internal view returns (bytes32)EIP-712 domain:
HinkalHelper, version1.0.0.Encoding binds call intent to the current
externalAddressand token set.
performHinkalChecks — The Preflight Gate
function performHinkalChecks(CircomData circomData, Dimensions dimensions, address sender)
external view returns (uint256[] inputForCircom)Sequence:
Timestamp freshness check (commented for dev/forking convenience).
Sender/relay consistency:
Either relayed (nonzero relay,
originalSender == 0) or direct (relay zero,originalSender == sender).
Calldata integrity:
CircomDataBuilder.getHashedCalldata(circomData) == circomData.calldataHash.Relay policy:
relayerIsValid(circomData.relay)andcheckRelayFeeParameters.Dimensions and shape:
dimensionsCheck.Token registry and access token requirement:
If any token exceeds limit and no signature provided (
v == 0): requireaccessToken.checkForRootHash(rootHashAccessToken, tx.origin).If signature provided: require
rootHashAccessToken == signatureData.accessKey.
EXECUTE metadata validation:
checkInLogicMetadata(signature + deadline + token set).Return
formInputForCircom(circomData)for proof verification.
Effect: This function is the canonical place where user intent, policy, and circuit shape are bound and validated before any value moves.
performSideEffects — Access Token Minting
function performSideEffects(CircomData circomData) external onlyHinkalCalls
mintAccessTokenIfNecessarywhich mints a token viaaccessToken.addToken(signatureData)when a signature is provided.
Integration Points
Called by
Hinkal.transactprior to proof verification.Provides
calculateRelayFeeand relay list fromRelayStorefor use in core flows.Supplies
isGasTokenand token limits viaERC20TokenRegistry.Builds the Groth16 public input vector consistent with the circuit.
Reverts and Errors
"Unauthorized relay" / "Relay is not whitelisted"
"invalid value for originalSender"
"Calldata Hash Integrity Check Failed"
"FeeToken should be gas token"
"fee token should be included in erc20Token array"
Dimension mismatches (token/nullifier/commitment counts)
"Extra-randomization cannot be zero"
"spend approvals should be zero" (when action not allowed to spend)
"Wallet External Id should be choosen"
"invalid inLogic Signature" / "deadline passed" / "missing token from parsedMetadata"
Access token checks: "Access Token Root Hash is Incorrect" / "accessKey must be used"
Security Notes
EIP-712 binding of EXECUTE intent prevents replay across addresses or altered call data.
Relay control plus registry policy ensure gas economics are enforced out-of-circuit.
All checks are read-only and must pass before proof verification and state mutations in
Hinkal.sol.
Last updated