diff --git a/public/api/ace/coordinator/openapi.json b/public/api/ace/coordinator/openapi.json new file mode 100644 index 00000000000..5993848cb13 --- /dev/null +++ b/public/api/ace/coordinator/openapi.json @@ -0,0 +1,10454 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "Chainlink ACE Coordinator API", + "version": "0.1.0", + "description": "The Coordinator API is the control plane for ACE. Use it to create and manage all ACE resources for your organization." + }, + "servers": [ + { + "url": "https://ace.api.chain.link/v1", + "description": "Production" + } + ], + "components": { + "schemas": { + "ActionValidatorResponse": { + "type": "object", + "required": ["chain_selector", "validator_address"], + "properties": { + "chain_selector": { + "type": "string", + "description": "Chain selector identifying the network" + }, + "validator_address": { + "type": "string", + "description": "Deployed CertifiedActionDONValidator contract address on this chain" + } + } + }, + "AnyPolicyImplementationResponse": { + "oneOf": [ + { + "$ref": "#/components/schemas/PolicyImplementation" + }, + { + "$ref": "#/components/schemas/OffchainPolicyImplementation" + } + ] + }, + "AnyPolicyResponse": { + "oneOf": [ + { + "$ref": "#/components/schemas/Policy" + }, + { + "$ref": "#/components/schemas/OffchainPolicyResponse" + } + ], + "discriminator": { + "propertyName": "policy_kind", + "mapping": { + "onchain": "#/components/schemas/Policy", + "offchain": "#/components/schemas/OffchainPolicyResponse" + } + } + }, + "ApiErrorResponse": { + "type": "object", + "required": ["error", "message"], + "properties": { + "message": { + "type": "string", + "description": "Message of the response" + }, + "error": { + "type": "string", + "description": "Error type", + "enum": ["Already exists", "Internal error", "Not found", "Bad request", "Unauthorized", "Forbidden"] + } + } + }, + "ApplyOnchainPolicy": { + "type": "object", + "required": ["chain_selector", "current_policy_config_version"], + "properties": { + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "current_policy_config_version": { + "type": "string", + "format": "uint64", + "description": "Current policy config version on chain" + } + } + }, + "ChainSelector": { + "type": "string", + "format": "uint64", + "description": "Chain selector" + }, + "ConfigureDataValidatorChains": { + "type": "object", + "required": ["chain_selector", "current_config_version"], + "properties": { + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "current_config_version": { + "type": "string", + "format": "uint64", + "description": "Expected current config version on-chain (optimistic concurrency)" + } + } + }, + "ContractConfigSchema": { + "type": "object", + "additionalProperties": {}, + "description": "JSON Schema draft-07 document defining configurable parameters for an on-chain contract\nimplementation.\n" + }, + "CreateAnyPolicyRequest": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreatePolicyRequest" + }, + { + "$ref": "#/components/schemas/CreateOffchainPolicyRequest" + } + ], + "description": "Create an onchain or offchain policy. `policy_kind` is optional and defaults to `\"onchain\"` if omitted. Set `policy_kind` to `\"offchain\"` to create an offchain policy.\n" + }, + "CreateCredentialInput": { + "type": "object", + "required": ["credential_type_id"], + "properties": { + "credential_type_id": { + "$ref": "#/components/schemas/Id" + }, + "credential_data": { + "description": "Required when the credential type is linked to a data schema (JSON object or array per data schema)" + }, + "external_unique_id": { + "type": "string", + "description": "External unique identifier for the credential" + }, + "expires_at": { + "type": "integer", + "format": "int64", + "description": "Expiration timestamp of the credential" + } + } + }, + "CreateCredentialRegistryInput": { + "type": "object", + "required": ["name", "chain_selector", "description"], + "properties": { + "name": { + "type": "string", + "description": "Name of the credential registry" + }, + "address": { + "$ref": "#/components/schemas/EthAddress", + "description": "Optional address. If not provided, the registry will be deployed and status will be creation_pending" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "description": { + "type": "string", + "description": "Description of the credential registry" + } + } + }, + "CreateDataValidatorRequest": { + "type": "object", + "required": ["name", "data_validator_implementation_id"], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "data_validator_implementation_id": { + "$ref": "#/components/schemas/Id" + }, + "onchain_data_validators": { + "type": "array", + "description": "On-chain deployments; each chain_selector may appear at most once", + "items": { + "$ref": "#/components/schemas/CreateOnchainDataValidatorRequest" + } + } + } + }, + "CreateExtractorRequest": { + "type": "object", + "required": ["name", "supported_function_signatures", "outputs"], + "properties": { + "name": { + "type": "string", + "description": "Human-readable name of the extractor" + }, + "supported_function_signatures": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Function signatures that the extractor supports" + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtractorOutputInput" + } + }, + "onchain_extractors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainExtractorInput" + } + } + } + }, + "CreateIdentitiesBatchRequest": { + "type": "object", + "required": ["identities"], + "properties": { + "identities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateIdentityRequest" + }, + "description": "List of identities to create" + } + } + }, + "CreateIdentitiesBatchResponse": { + "type": "object", + "required": ["results"], + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateIdentityResult" + }, + "description": "List of identity creation results" + } + } + }, + "CreateIdentityRegistryInput": { + "type": "object", + "required": ["name", "chain_selector", "description"], + "properties": { + "name": { + "type": "string", + "description": "Name of the identity registry" + }, + "address": { + "$ref": "#/components/schemas/EthAddress", + "description": "Optional address. If not provided, the registry will be deployed and status will be creation_pending" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "description": { + "type": "string", + "description": "Description of the identity registry" + } + } + }, + "CreateIdentityRequest": { + "type": "object", + "required": ["title", "entity_id", "registry_id", "onchain_identities"], + "properties": { + "title": { + "type": "string", + "description": "Human-readable title of the identity" + }, + "description": { + "type": "string", + "description": "Description of the identity" + }, + "entity_id": { + "type": "string", + "description": "Entity ID of the identity" + }, + "registry_id": { + "$ref": "#/components/schemas/Id" + }, + "onchain_identities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainIdentityInput" + }, + "description": "Onchain identities to register with the identity" + }, + "credentials": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateCredentialInput" + }, + "description": "Optional list of credentials to create for the new identity" + } + } + }, + "CreateIdentityResult": { + "type": "object", + "required": ["status"], + "properties": { + "identity": { + "$ref": "#/components/schemas/Identity" + }, + "status": { + "$ref": "#/components/schemas/CreateIdentityResultStatus" + }, + "error": { + "type": "string", + "description": "Error message if creation failed" + } + } + }, + "CreateIdentityResultStatus": { + "type": "string", + "description": "Status of the identity creation", + "enum": ["created", "failed"] + }, + "CreateOffchainPolicyRequest": { + "type": "object", + "required": ["type", "name", "policy_engine_id", "onchain_policies", "config"], + "properties": { + "policy_kind": { + "type": "string", + "enum": ["offchain"], + "description": "Optional. Set to \"offchain\" to create an offchain policy. Defaults to \"onchain\" if omitted." + }, + "type": { + "type": "string", + "enum": ["wallet_risk_scoring"], + "description": "Managed offchain policy MVP type. ACE Beta supports wallet_risk_scoring. Contact your Chainlink representative for setup assistance." + }, + "name": { + "type": "string", + "description": "Human-readable policy name" + }, + "policy_engine_id": { + "$ref": "#/components/schemas/Id", + "description": "ID of the onchain PolicyEngine this policy is associated with" + }, + "onchain_policies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateOnchainTargetProtectionRequest" + }, + "minItems": 1, + "description": "Chain selectors to deploy the CertifiedActionDONValidator on (one deployment per chain)" + }, + "config": { + "type": "object", + "additionalProperties": true, + "description": "Wallet screening configuration. Use secret_name, addresses_to_check, risk_threshold, block_unknown, optional category_filters, and optional fail_mode." + } + } + }, + "CreateOnchainDataValidatorRequest": { + "type": "object", + "required": ["chain_selector"], + "properties": { + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "initial_config": { + "type": "object", + "description": "Initial configuration for schema-derived keys", + "additionalProperties": true + } + } + }, + "CreateOnchainPolicyRequest": { + "type": "object", + "required": ["chain_selector"], + "properties": { + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "initial_config": { + "type": "object", + "description": "Initial configuration values for properties listed in initial_configs", + "additionalProperties": true + } + } + }, + "CreateOnchainTargetProtectionRequest": { + "type": "object", + "required": ["chain_selector"], + "properties": { + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + } + } + }, + "CreatePolicyEngineRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Human-readable name of the policy engine" + }, + "description": { + "type": "string", + "description": "Human-readable description of the policy engine" + }, + "extractor_ids": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + }, + "onchain_policy_engines": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainPolicyEngineInput" + } + } + }, + "required": ["name", "onchain_policy_engines"] + }, + "CreatePolicyRequest": { + "type": "object", + "required": ["name", "policy_implementation_id", "policy_engine_id"], + "properties": { + "policy_kind": { + "type": "string", + "enum": ["onchain"], + "description": "Optional. Identifies this as an onchain policy creation request. Defaults to \"onchain\" if omitted." + }, + "name": { + "type": "string", + "description": "Human-readable name of the policy instance" + }, + "description": { + "type": "string", + "description": "Description of the policy instance" + }, + "policy_implementation_id": { + "$ref": "#/components/schemas/Id" + }, + "policy_engine_id": { + "$ref": "#/components/schemas/Id" + }, + "onchain_policies": { + "type": "array", + "description": "On-chain deployments; each chain_selector may appear at most once", + "items": { + "$ref": "#/components/schemas/CreateOnchainPolicyRequest" + } + } + } + }, + "CreateRegistryAccessGrantRequest": { + "type": "object", + "required": ["grantee_org_id"], + "properties": { + "grantee_org_id": { + "$ref": "#/components/schemas/OrgId", + "description": "Organization ID to grant registry access to" + } + } + }, + "CreateRegistryRequest": { + "type": "object", + "required": ["name", "description"], + "properties": { + "name": { + "type": "string", + "description": "Name of the registry" + }, + "description": { + "type": "string", + "description": "Description of the registry" + }, + "identity_registries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateIdentityRegistryInput" + }, + "description": "List of identity registries to create and associate with this registry" + }, + "credential_registries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateCredentialRegistryInput" + }, + "description": "List of credential registries to create and associate with this registry" + } + } + }, + "CreateTargetProtectionRequest": { + "type": "object", + "required": ["function_signature", "policy_instance_id"], + "description": "For onchain policies, `policy_instance_id` is the onchain policy instance ID and `policy_kind` may be omitted (defaults to \"onchain\"). For offchain (CADV) policies, `policy_instance_id` is the offchain policy ID and `policy_kind` must be \"offchain\".\n", + "properties": { + "function_signature": { + "type": "string", + "description": "Function signature of the target protection" + }, + "policy_instance_id": { + "$ref": "#/components/schemas/Id", + "description": "ID of the policy to protect. For onchain policies this is the policy instance ID; for offchain policies this is the offchain policy ID." + }, + "policy_kind": { + "type": "string", + "enum": ["onchain", "offchain"], + "description": "Kind of policy being protected. Defaults to \"onchain\" if omitted." + }, + "desired_position": { + "type": "integer", + "description": "Desired position of the target protection" + }, + "onchain_target_protections": { + "type": "array", + "nullable": true, + "items": { + "$ref": "#/components/schemas/CreateOnchainTargetProtectionRequest" + } + }, + "extractor_output_ids": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + } + } + }, + "CreateWalletRequest": { + "type": "object", + "required": ["owner_address", "chain_selector"], + "properties": { + "owner_address": { + "$ref": "#/components/schemas/EthAddress" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "address": { + "type": "array", + "description": "ECDSA signer addresses allowed to sign transactions for this wallet. Only required for self-sign organizations.", + "items": { + "$ref": "#/components/schemas/EthAddress" + } + } + } + }, + "CreateWalletsRequest": { + "type": "object", + "required": ["wallets"], + "properties": { + "wallets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateWalletRequest" + } + } + } + }, + "CreatedAt": { + "type": "integer", + "format": "int64", + "description": "Timestamp of the creation" + }, + "Credential": { + "type": "object", + "required": [ + "id", + "credential_type_id", + "external_unique_id", + "identity_id", + "chain_selectors", + "created_at", + "updated_at" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "credential_type_id": { + "$ref": "#/components/schemas/Id" + }, + "credential_type": { + "$ref": "#/components/schemas/CredentialType" + }, + "credential_data": { + "nullable": true, + "description": "Validated credential payload JSON when the credential type is linked to a data schema; null otherwise" + }, + "external_unique_id": { + "type": "string", + "description": "External unique identifier for the credential" + }, + "expires_at": { + "type": "integer", + "format": "int64", + "description": "Expiration timestamp of the credential" + }, + "identity_id": { + "$ref": "#/components/schemas/Id" + }, + "onchain_credentials": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainCredential" + } + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "archived_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp when the credential was archived (null if not archived)" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + } + }, + "CredentialRegistry": { + "type": "object", + "required": [ + "id", + "address", + "name", + "description", + "created_at", + "updated_at", + "org_id", + "chain_selector", + "registry_id", + "status" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "address": { + "$ref": "#/components/schemas/EthAddress" + }, + "name": { + "type": "string", + "description": "Human-readable name of the credential registry" + }, + "description": { + "type": "string", + "description": "Human-readable description of the credential registry" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + }, + "org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "registry_id": { + "$ref": "#/components/schemas/Id" + }, + "status": { + "$ref": "#/components/schemas/RegistryStatus" + } + } + }, + "CredentialStatus": { + "type": "string", + "description": "Status of the credential (active or archived)", + "enum": ["active", "archived"] + }, + "CredentialType": { + "type": "object", + "required": [ + "id", + "registry_id", + "title", + "credential_type_hash", + "chain_selectors", + "created_at", + "updated_at" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "registry_id": { + "$ref": "#/components/schemas/Id" + }, + "title": { + "type": "string", + "description": "Title of the credential type" + }, + "description": { + "type": "string", + "description": "Description of the credential type" + }, + "credential_type": { + "type": "string", + "description": "Type of the credential (e.g., \"common.KYC\")" + }, + "credential_type_hash": { + "type": "string", + "description": "Hash of the credential type" + }, + "archived_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp when archived (null if not archived)", + "nullable": true + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "data_schema": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/CredentialTypeEmbeddedDataSchema" + } + ] + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + } + }, + "CredentialTypeEmbeddedDataSchema": { + "type": "object", + "description": "Data schema linked to a credential type for credential payload validation", + "required": ["id", "name", "description", "schema"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "schema": { + "$ref": "#/components/schemas/PolicyConfigSchema" + } + } + }, + "CredentialTypePatchStatus": { + "type": "string", + "description": "Status for credential type PATCH (archive only)", + "enum": ["archived"] + }, + "DataValidator": { + "type": "object", + "required": [ + "id", + "name", + "description", + "data_validator_implementation_id", + "onchain_configs", + "chain_selectors" + ], + "description": "A data validator instance (deployment aggregate across chains).", + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "data_validator_implementation_id": { + "$ref": "#/components/schemas/Id" + }, + "onchain_configs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataValidatorOnchainConfig" + } + }, + "onchain_data_validators": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainDataValidatorInstance" + }, + "description": "Deprecated — prefer onchain_configs for config grouping; retained for backward compatibility." + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "ongoing_config_changes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OngoingConfigChange" + } + }, + "archived_at": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Unix timestamp when archived" + } + } + }, + "DataValidatorOnchainConfig": { + "type": "object", + "required": ["id", "chain_selectors", "config_json"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id", + "description": "Data validator config log ID" + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + }, + "description": "Chain selectors sharing this config log" + }, + "config_json": { + "type": "object", + "additionalProperties": true, + "description": "Configuration JSON for this log" + } + } + }, + "DataValidatorPatchStatus": { + "type": "string", + "description": "PATCH body for archiving a data validator instance", + "enum": ["archived"] + }, + "EthAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Ethereum address" + }, + "Extractor": { + "type": "object", + "required": [ + "id", + "name", + "supported_function_signatures", + "chain_selectors", + "outputs", + "created_at", + "updated_at" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "name": { + "type": "string", + "description": "Human-readable name of the extractor" + }, + "org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "supported_function_signatures": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Function signatures that the extractor supports" + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtractorOutput" + } + }, + "onchain_extractors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainExtractor" + } + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + }, + "archived_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp when archived (null if not archived)", + "nullable": true + } + } + }, + "ExtractorOutput": { + "type": "object", + "required": ["id", "extractor_id", "name", "type", "created_at", "updated_at"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "extractor_id": { + "$ref": "#/components/schemas/Id" + }, + "name": { + "type": "string", + "description": "Human-readable name of the extractor output" + }, + "type": { + "type": "string", + "description": "Type of the extractor output, e.g. \"address\", \"uint256\", \"bytes32\"" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + } + }, + "ExtractorOutputInput": { + "type": "object", + "required": ["name", "type"], + "properties": { + "name": { + "type": "string", + "description": "Human-readable name of the extractor output" + }, + "type": { + "type": "string", + "description": "Type of the extractor output, e.g. \"address\", \"uint256\", \"bytes32\"" + } + } + }, + "ExtractorPatchStatus": { + "type": "string", + "description": "Status for extractor PATCH (archive only)", + "enum": ["archived"] + }, + "ExtractorRegistration": { + "type": "object", + "required": [ + "id", + "extractor_id", + "policy_engine_id", + "onchain_extractor_registrations", + "created_at", + "updated_at" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "extractor_id": { + "$ref": "#/components/schemas/Id" + }, + "policy_engine_id": { + "$ref": "#/components/schemas/Id" + }, + "onchain_extractor_registrations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainExtractorRegistration" + } + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + } + }, + "HealthCheck": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "ok" + } + }, + "required": ["status"] + }, + "Id": { + "type": "string", + "format": "uuid", + "description": "ID" + }, + "Identity": { + "type": "object", + "required": ["id", "title", "ccid", "chain_selectors", "created_at", "updated_at"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "title": { + "type": "string", + "description": "Human-readable title of the identity" + }, + "description": { + "type": "string", + "description": "Description of the identity" + }, + "entity_id": { + "type": "string", + "description": "Entity ID of the identity" + }, + "ccid": { + "type": "string", + "description": "CCID of the identity" + }, + "registryId": { + "$ref": "#/components/schemas/Id" + }, + "onchain_identities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainIdentity" + } + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "archived_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp when the identity was archived (null if not archived)" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + } + }, + "IdentityPatchStatus": { + "type": "string", + "description": "Status for identity PATCH (archive only)", + "enum": ["archived"] + }, + "IdentityRegistry": { + "type": "object", + "required": [ + "id", + "address", + "name", + "description", + "created_at", + "updated_at", + "org_id", + "chain_selector", + "registry_id", + "status" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "address": { + "$ref": "#/components/schemas/EthAddress" + }, + "name": { + "type": "string", + "description": "Human-readable name of the identity registry" + }, + "description": { + "type": "string", + "description": "Human-readable description of the identity registry" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + }, + "org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "registry_id": { + "$ref": "#/components/schemas/Id" + }, + "status": { + "$ref": "#/components/schemas/RegistryStatus" + } + } + }, + "IdentityStatus": { + "type": "string", + "description": "Status of the identity", + "enum": ["creation_pending", "creation_failed", "created", "removal_pending", "removal_failed", "removed"] + }, + "JsonPatchOperation": { + "type": "object", + "required": ["op", "path"], + "properties": { + "op": { + "type": "string", + "description": "JSON Patch operation type", + "enum": ["add", "remove", "replace", "move", "copy", "test"] + }, + "path": { + "type": "string", + "description": "JSON pointer path to patch (RFC 6902)" + }, + "value": { + "description": "Value for the operation (can be any JSON type: string, number, boolean, object, array, or null)", + "nullable": true + }, + "from": { + "type": "string", + "description": "Source path for move/copy operations (RFC 6902)" + } + } + }, + "ListCredentialTypesResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "required": ["credential_types"], + "properties": { + "credential_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CredentialType" + } + } + } + } + ] + }, + "ListCredentialsResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "required": ["credentials"], + "properties": { + "credentials": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Credential" + } + } + } + } + ] + }, + "ListDataValidatorsResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "properties": { + "data_validators": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataValidator" + } + } + } + } + ] + }, + "ListExtractorsResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "required": ["extractors"], + "properties": { + "extractors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Extractor" + } + } + } + } + ] + }, + "ListIdentitiesResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "required": ["identities"], + "properties": { + "identities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Identity" + } + } + } + } + ] + }, + "ListNetworksResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "required": ["networks"], + "properties": { + "networks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Network" + } + } + } + } + ] + }, + "ListOffchainPolicyProtectionsResponse": { + "type": "object", + "required": ["protections", "total", "total_pages", "page"], + "properties": { + "protections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OffchainPolicyProtectionResponse" + } + }, + "total": { + "type": "integer" + }, + "total_pages": { + "type": "integer" + }, + "page": { + "type": "integer" + } + } + }, + "ListPoliciesResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "properties": { + "policies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnyPolicyResponse" + } + } + } + } + ] + }, + "ListPolicyEnginesResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "required": ["policy_engines"], + "properties": { + "policy_engines": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PolicyEngine" + } + } + } + } + ] + }, + "ListPolicyImplementationsResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "required": ["implementations"], + "properties": { + "implementations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnyPolicyImplementationResponse" + } + } + } + } + ] + }, + "ListRegistriesResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "required": ["registries"], + "properties": { + "registries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Registry" + } + } + } + } + ] + }, + "ListRegistryAccessGrantsResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "required": ["access_grants"], + "properties": { + "access_grants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RegistryAccessGrant" + } + } + } + } + ] + }, + "ListResponseData": { + "type": "object", + "required": ["total", "total_pages", "page"], + "properties": { + "total": { + "type": "integer", + "description": "Total number of items" + }, + "total_pages": { + "type": "integer", + "description": "Total number of pages" + }, + "page": { + "type": "integer", + "description": "Current page" + } + } + }, + "ListTargetProtectionsResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "required": ["target_protections"], + "properties": { + "target_protections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TargetProtection" + } + } + } + } + ] + }, + "ListTargetsResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/ListResponseData" + }, + { + "type": "object", + "required": ["targets"], + "properties": { + "targets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Target" + } + } + } + } + ] + }, + "ListWalletsResponse": { + "type": "object", + "required": ["wallets"], + "properties": { + "wallets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Wallet" + } + } + } + }, + "MergeTargetRequest": { + "type": "object", + "required": ["source_target_ids"], + "properties": { + "source_target_ids": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + }, + "minItems": 1, + "description": "The IDs of the source targets whose onchain targets will be transferred to the destination target." + } + } + }, + "Network": { + "type": "object", + "required": [ + "id", + "name", + "chain_id", + "chain_selector", + "chain_family", + "is_testnet", + "created_at", + "updated_at" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "name": { + "type": "string", + "description": "Human-readable name of the network" + }, + "chain_id": { + "type": "string", + "description": "Chain ID of the network" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "chain_family": { + "type": "string", + "description": "Chain family of the network" + }, + "is_testnet": { + "type": "boolean", + "description": "Whether this network is a testnet (not mainnet)" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + } + }, + "OffchainPolicyImplementation": { + "type": "object", + "required": ["id", "name", "type", "config", "created_at", "updated_at"], + "properties": { + "policy_kind": { + "type": "string", + "enum": ["offchain"], + "description": "Discriminator identifying this as an offchain policy implementation" + }, + "id": { + "$ref": "#/components/schemas/Id" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "description": "Unique managed policy type identifier, for example wallet_risk_scoring" + }, + "config": { + "type": "object", + "additionalProperties": true, + "description": "Implementation config including provider, provider_url, and config_schema" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "OffchainPolicyProtectionResponse": { + "type": "object", + "required": ["id", "offchain_policy_id", "function_signature", "function_selector", "status", "created_at"], + "properties": { + "id": { + "type": "string" + }, + "offchain_policy_id": { + "type": "string" + }, + "function_signature": { + "type": "string" + }, + "function_selector": { + "type": "string", + "description": "4-byte hex selector, e.g. \"0xa9059cbb\"" + }, + "status": { + "type": "string", + "enum": ["pending", "active", "failed", "removing", "removed"] + }, + "onchain_target_protections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainTargetProtection" + }, + "description": "Per-chain status of the addPolicyAt() registration on the PolicyEngine" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "OffchainPolicyResponse": { + "type": "object", + "required": ["id", "type", "name", "config", "deployment_status", "created_at", "policy_kind"], + "properties": { + "policy_kind": { + "type": "string", + "enum": ["offchain"], + "description": "Discriminator identifying this as an offchain policy" + }, + "id": { + "type": "string", + "description": "Policy ID" + }, + "type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "config": { + "type": "object", + "additionalProperties": true + }, + "policy_engine_id": { + "type": "string", + "description": "ID of the onchain PolicyEngine this policy is associated with" + }, + "policy_implementation_id": { + "type": "string", + "description": "ID of the offchain policy implementation (template) used to create this policy" + }, + "action_validators": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ActionValidatorResponse" + }, + "description": "Per-chain CADV deployment info (chain selector + contract address). Populated once deployment_status is active." + }, + "deployment_status": { + "type": "string", + "enum": ["pending", "deploying", "active", "failed", "removing", "removed", "config_updating"] + }, + "workflow_id": { + "type": "string" + }, + "workflow_name": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "OnChainCredentialStatus": { + "type": "string", + "description": "Status of the onchain credential\n", + "enum": [ + "creation_pending", + "creation_failed", + "created", + "update_pending", + "update_failed", + "removal_pending", + "removal_failed", + "removed" + ] + }, + "OnchainConfig": { + "type": "object", + "required": ["id", "chain_selectors", "config_json"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id", + "description": "Policy config log ID" + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + }, + "description": "Array of chain selectors that use this config" + }, + "config_json": { + "type": "object", + "additionalProperties": true, + "description": "The policy configuration as JSON" + } + } + }, + "OnchainCredential": { + "type": "object", + "required": ["id", "chain_selector", "status"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "status": { + "$ref": "#/components/schemas/OnChainCredentialStatus" + }, + "expires_at": { + "type": "integer", + "format": "int64", + "description": "Expiration timestamp of the credential" + } + } + }, + "OnchainDataValidatorInstance": { + "type": "object", + "required": ["id", "chain_selector", "data_validator_config_version", "status"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "address": { + "$ref": "#/components/schemas/EthAddress" + }, + "data_validator_config_version": { + "type": "string", + "format": "uint64", + "description": "Version of the data validator configuration on-chain" + }, + "status": { + "$ref": "#/components/schemas/OnchainDataValidatorInstanceStatus" + } + } + }, + "OnchainDataValidatorInstanceStatus": { + "type": "string", + "description": "Status of an on-chain data validator deployment", + "enum": ["creation_pending", "creation_failed", "created", "update_pending", "update_failed"] + }, + "OnchainExtractor": { + "type": "object", + "required": ["id", "extractor_id", "chain_selector", "address", "created_at", "updated_at"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "extractor_id": { + "$ref": "#/components/schemas/Id" + }, + "chain_selector": { + "type": "string", + "description": "Chain selector" + }, + "address": { + "type": "string", + "description": "Ethereum address" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + } + }, + "OnchainExtractorInput": { + "type": "object", + "required": ["chain_selector", "address"], + "properties": { + "chain_selector": { + "type": "string", + "description": "Chain selector" + }, + "address": { + "type": "string", + "description": "Ethereum address" + } + } + }, + "OnchainExtractorRegistration": { + "type": "object", + "required": ["id", "chain_selector", "status", "created_at", "updated_at"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "status": { + "$ref": "#/components/schemas/OnchainStatus" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + } + }, + "OnchainIdentity": { + "type": "object", + "required": ["id", "chain_selector", "identity_id", "address", "status", "created_at", "updated_at"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "identity_id": { + "$ref": "#/components/schemas/Id" + }, + "address": { + "$ref": "#/components/schemas/EthAddress" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "status": { + "$ref": "#/components/schemas/IdentityStatus" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + } + }, + "OnchainIdentityInput": { + "type": "object", + "required": ["chain_selector", "address"], + "properties": { + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "address": { + "$ref": "#/components/schemas/EthAddress" + } + } + }, + "OnchainPolicyEngine": { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "address": { + "$ref": "#/components/schemas/EthAddress" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "status": { + "$ref": "#/components/schemas/OnchainPolicyEngineStatus" + } + }, + "required": ["id", "address", "chain_selector", "status"] + }, + "OnchainPolicyEngineInput": { + "type": "object", + "properties": { + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "required": ["chain_selector"] + }, + "OnchainPolicyEngineStatus": { + "type": "string", + "description": "Status of the onchain policy engine", + "enum": ["creation_pending", "creation_failed", "created"] + }, + "OnchainPolicyImplementation": { + "type": "object", + "required": ["chain_selector", "address"], + "properties": { + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "address": { + "$ref": "#/components/schemas/EthAddress" + } + } + }, + "OnchainPolicyInstance": { + "type": "object", + "required": ["id", "chain_selector", "policy_config_version", "out_of_sync", "status"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "address": { + "$ref": "#/components/schemas/EthAddress" + }, + "policy_config_version": { + "type": "string", + "format": "uint64", + "description": "Version of the policy configuration" + }, + "out_of_sync": { + "type": "boolean", + "description": "Whether the policy configuration is out of sync due to an external configuration change" + }, + "status": { + "$ref": "#/components/schemas/OnchainPolicyInstanceStatus" + } + } + }, + "OnchainPolicyInstanceStatus": { + "type": "string", + "description": "Status of the onchain policy instance", + "enum": ["creation_pending", "creation_failed", "created", "update_pending", "update_failed"] + }, + "OnchainStatus": { + "type": "string", + "description": "Status of the onchain entity", + "enum": ["active", "inactive"] + }, + "OnchainTarget": { + "type": "object", + "required": ["id", "chain_selector", "address"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "address": { + "$ref": "#/components/schemas/EthAddress" + }, + "default_allow": { + "type": "boolean", + "description": "Default policy allow state on chain", + "default": true + } + } + }, + "OnchainTargetProtection": { + "type": "object", + "required": ["id", "chain_selector"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "position": { + "type": "integer", + "description": "Position of the target protection in for this specific chain" + }, + "status": { + "$ref": "#/components/schemas/TargetProtectionStatus" + } + } + }, + "OngoingConfigChange": { + "type": "object", + "required": ["chain_selectors", "patches"], + "properties": { + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + }, + "description": "Array of chain selectors for this configuration change" + }, + "patches": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JsonPatchOperation" + }, + "description": "Array of JSON patch operations to apply" + } + } + }, + "OrgId": { + "type": "string", + "description": "Organization ID (e.g. from CRE). Alphanumeric and limited symbols, max 100 characters.", + "pattern": "^[a-zA-Z0-9._-]{1,100}$" + }, + "PatchCredentialRequest": { + "type": "object", + "description": "Either status (to archive) OR update fields (external_unique_id, expires_at). Mutually exclusive.\n- If status is passed, other fields must not be passed (400).\n- If status=archived and credential is already archived, returns 400.\n- Only status=archived triggers archive; status=active is invalid for PATCH.\n- If external_unique_id or expires_at are set, reuses update logic.\n", + "properties": { + "status": { + "$ref": "#/components/schemas/CredentialStatus", + "description": "Set to \"archived\" to archive. Mutually exclusive with update fields." + }, + "external_unique_id": { + "type": "string", + "description": "External unique identifier. Mutually exclusive with status." + }, + "expires_at": { + "type": "integer", + "format": "int64", + "description": "Expiration timestamp. Mutually exclusive with status." + } + } + }, + "PatchCredentialTypeRequest": { + "type": "object", + "description": "Either status (to archive) OR update fields (title, description). Mutually exclusive.\n- If status is passed, other fields must not be passed (400).\n- If status=archived and credential type is already archived, returns 400.\n- Only status=archived triggers archive.\n- If title or description are set, internally calls UpdateCredentialType (PUT).\n", + "properties": { + "status": { + "$ref": "#/components/schemas/CredentialTypePatchStatus", + "description": "Set to \"archived\" to archive. Mutually exclusive with update fields." + }, + "title": { + "type": "string", + "description": "Human-readable title. Mutually exclusive with status." + }, + "description": { + "type": "string", + "description": "Human-readable description of the credential type. Mutually exclusive with status." + } + } + }, + "PatchDataValidatorConfigsRequest": { + "type": "object", + "properties": { + "patches": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JsonPatchOperation" + }, + "description": "JSON Patch operations (RFC 6902)" + }, + "on_chains": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConfigureDataValidatorChains" + }, + "description": "Chains to apply patches on (must include at least one entry)" + } + } + }, + "PatchDataValidatorRequest": { + "type": "object", + "required": ["status"], + "properties": { + "status": { + "$ref": "#/components/schemas/DataValidatorPatchStatus" + } + } + }, + "PatchExtractorRequest": { + "type": "object", + "required": ["status"], + "description": "Set status to \"archived\" to archive the extractor.", + "properties": { + "status": { + "$ref": "#/components/schemas/ExtractorPatchStatus", + "description": "Set to \"archived\" to archive." + } + } + }, + "PatchIdentityRequest": { + "type": "object", + "description": "Either status (to archive) OR update fields (title, description, onchain_identities). Mutually exclusive.\n- If status is passed, other fields must not be passed (400).\n- If status=archived and identity is already archived, returns 400.\n- Only status=archived triggers archive.\n- If title, description, or onchain_identities are set, internally calls UpdateIdentity (PUT).\n", + "properties": { + "status": { + "$ref": "#/components/schemas/IdentityPatchStatus", + "description": "Set to \"archived\" to archive. Mutually exclusive with update fields." + }, + "title": { + "type": "string", + "description": "Human-readable title. Mutually exclusive with status." + }, + "description": { + "type": "string", + "description": "Description of the identity. Mutually exclusive with status." + }, + "onchain_identities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainIdentityInput" + }, + "description": "Onchain identities to register. Mutually exclusive with status." + } + } + }, + "PatchPolicyConfigsRequest": { + "type": "object", + "properties": { + "patches": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JsonPatchOperation" + }, + "description": "Array of JSON Patch operations to apply" + }, + "apply_onchain_policies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplyOnchainPolicy" + }, + "description": "Array of onchain policies to apply the patches to" + } + } + }, + "PatchPolicyEngineRequest": { + "type": "object", + "required": ["status"], + "description": "Set status to \"archived\" to archive the policy engine.", + "properties": { + "status": { + "$ref": "#/components/schemas/PolicyEnginePatchStatus", + "description": "Set to \"archived\" to archive." + } + } + }, + "PatchPolicyImplementationRequest": { + "type": "object", + "required": ["status"], + "description": "Set status to \"archived\" to archive the policy implementation.", + "properties": { + "status": { + "$ref": "#/components/schemas/PolicyImplementationPatchStatus", + "description": "Set to \"archived\" to archive." + } + } + }, + "PatchPolicyRequest": { + "type": "object", + "required": ["status"], + "description": "Set status to \"archived\" to archive the policy. Use policy_kind to distinguish onchain (default) from offchain policies.", + "properties": { + "status": { + "$ref": "#/components/schemas/PolicyPatchStatus", + "description": "Set to \"archived\" to archive." + }, + "policy_kind": { + "type": "string", + "enum": ["onchain", "offchain"], + "description": "Kind of policy to archive. Defaults to \"onchain\" if omitted." + } + } + }, + "PatchRegistryAccessGrantRequest": { + "type": "object", + "required": ["status"], + "properties": { + "status": { + "type": "string", + "enum": ["revoked"], + "description": "Target status. Currently only \"revoked\" is accepted." + } + } + }, + "PatchRegistryRequest": { + "type": "object", + "required": ["status"], + "description": "Set status to \"archived\" to archive the registry.", + "properties": { + "status": { + "$ref": "#/components/schemas/RegistryPatchStatus", + "description": "Set to \"archived\" to archive." + } + } + }, + "PatchTargetProtectionRequest": { + "type": "object", + "required": ["status"], + "description": "Set status to \"archived\" to archive the target protection.", + "properties": { + "status": { + "$ref": "#/components/schemas/TargetProtectionPatchStatus", + "description": "Set to \"archived\" to archive." + } + } + }, + "PatchTargetRequest": { + "type": "object", + "required": ["status"], + "description": "Set status to \"archived\" to archive the target.", + "properties": { + "status": { + "$ref": "#/components/schemas/TargetPatchStatus", + "description": "Set to \"archived\" to archive." + } + } + }, + "Policy": { + "type": "object", + "required": [ + "id", + "name", + "description", + "policy_engine_id", + "onchain_configs", + "chain_selectors", + "policy_kind" + ], + "properties": { + "policy_kind": { + "type": "string", + "enum": ["onchain"], + "description": "Discriminator identifying this as an onchain policy" + }, + "id": { + "$ref": "#/components/schemas/Id" + }, + "name": { + "type": "string", + "description": "Human-readable name of the policy instance" + }, + "description": { + "type": "string", + "description": "Description of the policy instance" + }, + "policy_implementation_id": { + "$ref": "#/components/schemas/Id" + }, + "policy_implementation_name": { + "type": "string", + "description": "Human-readable name of the policy implementation" + }, + "policy_engine_id": { + "$ref": "#/components/schemas/Id" + }, + "onchain_configs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainConfig" + }, + "description": "Array of onchain configurations grouped by config log ID" + }, + "onchain_policies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainPolicyInstance" + }, + "description": "Deprecated - use onchain_configs instead" + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "ongoing_config_changes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OngoingConfigChange" + }, + "description": "Array of ongoing configuration changes" + }, + "archived_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp when archived (null if not archived)", + "nullable": true + } + } + }, + "PolicyConfigSchema": { + "allOf": [ + { + "$ref": "#/components/schemas/ContractConfigSchema" + } + ], + "description": "JSON Schema draft-07 document defining the configurable parameters of this policy\nimplementation.\n" + }, + "PolicyEngine": { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "name": { + "type": "string", + "description": "Human-readable name of the policy engine" + }, + "description": { + "type": "string", + "description": "Human-readable description of the policy engine" + }, + "type": { + "$ref": "#/components/schemas/PolicyEngineType" + }, + "onchain_policy_engines": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainPolicyEngine" + } + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "extractor_registrations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtractorRegistration" + } + }, + "archived_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp when archived (null if not archived)", + "nullable": true + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + }, + "required": ["id", "name", "type", "chain_selectors", "created_at", "updated_at"] + }, + "PolicyEnginePatchStatus": { + "type": "string", + "description": "Status for policy engine PATCH (archive only)", + "enum": ["archived"] + }, + "PolicyEngineType": { + "type": "string", + "description": "Type of the policy engine", + "enum": ["standard", "system"] + }, + "PolicyImplementation": { + "type": "object", + "properties": { + "policy_kind": { + "type": "string", + "enum": ["onchain"], + "description": "Discriminator identifying this as an onchain policy implementation" + }, + "id": { + "$ref": "#/components/schemas/Id" + }, + "name": { + "type": "string", + "description": "Human-readable name of the policy implementation" + }, + "description": { + "type": "string", + "description": "Human-readable description of the policy implementation" + }, + "type": { + "$ref": "#/components/schemas/PolicyImplementationType" + }, + "policy_config_schema": { + "$ref": "#/components/schemas/PolicyConfigSchema" + }, + "onchain_policy_implementations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainPolicyImplementation" + } + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "parameters": { + "type": "array", + "description": "Processed policy run parameters with server-generated fields", + "items": { + "$ref": "#/components/schemas/PolicyRunParametersResponse" + } + }, + "archived_at": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Unix timestamp when archived; null if not archived" + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "Timestamp of the creation" + }, + "updated_at": { + "type": "integer", + "format": "int64", + "description": "Timestamp of the last update" + } + }, + "required": [ + "id", + "name", + "description", + "type", + "policy_config_schema", + "chain_selectors", + "parameters", + "created_at", + "updated_at" + ] + }, + "PolicyImplementationPatchStatus": { + "type": "string", + "description": "Status for policy implementation PATCH (archive only)", + "enum": ["archived"] + }, + "PolicyImplementationType": { + "type": "string", + "description": "Type of the policy implementation; system types are predefined, custom for user-defined.", + "enum": [ + "allow", + "bypass", + "certified_action_don_validator", + "grouped_identity_validator", + "identity_validator", + "interval", + "max", + "only_authorized_sender", + "pause", + "reject", + "role_based_access_control", + "secure_mint", + "volume", + "volume_rate", + "custom" + ] + }, + "PolicyPatchStatus": { + "type": "string", + "description": "Status for policy PATCH (archive only)", + "enum": ["archived"] + }, + "PolicyRunParametersResponse": { + "type": "object", + "description": "Policy parameter with server-generated fields (used in responses)", + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "name": { + "type": "string", + "description": "Human-readable name of the policy parameter" + }, + "type": { + "type": "string", + "description": "Type of the policy parameter" + }, + "position": { + "type": "integer", + "description": "Position of the policy parameter" + }, + "max": { + "type": "integer", + "description": "Maximum number of values" + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "Timestamp of the creation" + }, + "updated_at": { + "type": "integer", + "format": "int64", + "description": "Timestamp of the last update" + } + }, + "required": ["id", "name", "type", "position", "max", "created_at", "updated_at"] + }, + "RegisterCredentialRequest": { + "type": "object", + "required": ["credential_type_id", "identity_id"], + "properties": { + "credential_type_id": { + "$ref": "#/components/schemas/Id" + }, + "credential_data": { + "description": "Required when the credential type is linked to a data schema (JSON object or array per data schema)" + }, + "external_unique_id": { + "type": "string", + "description": "External unique identifier for the credential" + }, + "expires_at": { + "type": "integer", + "format": "int64", + "description": "Expiration timestamp of the credential" + }, + "identity_id": { + "$ref": "#/components/schemas/Id" + } + } + }, + "RegisterCredentialTypeRequest": { + "type": "object", + "required": ["registry_id", "title", "credential_type"], + "properties": { + "registry_id": { + "$ref": "#/components/schemas/Id" + }, + "title": { + "type": "string", + "description": "Human-readable title of the credential type" + }, + "description": { + "type": "string", + "description": "Human-readable description of the credential type" + }, + "credential_type": { + "type": "string", + "description": "Type of the credential (e.g., \"common.KYC\")" + }, + "data_schema_id": { + "$ref": "#/components/schemas/Id", + "description": "Optional shared or org-scoped data schema used to validate credential payload data" + } + } + }, + "RegisterOnchainTargetRequest": { + "type": "object", + "required": ["chain_selector", "address"], + "properties": { + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "address": { + "$ref": "#/components/schemas/EthAddress" + } + } + }, + "RegisterPolicyImplementationRequest": { + "type": "object", + "required": ["name", "description", "policy_config_schema"], + "properties": { + "name": { + "type": "string", + "description": "Human-readable name of the policy implementation" + }, + "description": { + "type": "string", + "description": "Human-readable description of the policy implementation" + }, + "policy_config_schema": { + "$ref": "#/components/schemas/PolicyConfigSchema" + }, + "onchain_policy_implementations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainPolicyImplementation" + } + } + } + }, + "RegisterTargetRequest": { + "type": "object", + "required": ["title", "policy_engine_id"], + "properties": { + "title": { + "type": "string", + "description": "Human-readable title of the target" + }, + "description": { + "type": "string", + "description": "Human-readable description of the target" + }, + "policy_engine_id": { + "$ref": "#/components/schemas/Id" + }, + "protected_methods": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Protected methods of the target" + }, + "onchain_targets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RegisterOnchainTargetRequest" + } + }, + "desired_default_allow": { + "type": "boolean", + "description": "Whether to allow transactions by default if no policy explicitly returns an Allow or Reject. Defaults to true.", + "default": true + }, + "metadata": { + "type": "object", + "description": "JSON metadata associated with the target", + "additionalProperties": true + } + } + }, + "Registry": { + "type": "object", + "required": ["id", "name", "description", "org_id", "created_at", "updated_at", "chain_selectors", "mode"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "name": { + "type": "string", + "description": "Name of the registry" + }, + "description": { + "type": "string", + "description": "Description of the registry" + }, + "org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + }, + "identity_registries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IdentityRegistry" + }, + "description": "List of identity registries associated with this registry" + }, + "credential_registries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CredentialRegistry" + }, + "description": "List of credential registries associated with this registry" + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "mode": { + "$ref": "#/components/schemas/RegistryMode" + }, + "archived_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp when archived (null if not archived)", + "nullable": true + }, + "access_type": { + "type": "string", + "enum": ["owned", "granted"], + "description": "Whether the caller owns this registry or has been granted access by another org. Only present when the caller is a grantee or when include_granted=true is used on the list endpoint.", + "nullable": true + } + } + }, + "RegistryAccessGrant": { + "type": "object", + "required": ["id", "grantee_org_id", "grantor_org_id", "status", "granted_at"], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "grantee_org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "grantor_org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "status": { + "type": "string", + "enum": ["active", "revoked"], + "description": "Current status of the access grant" + }, + "granted_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp when the grant was created" + }, + "revoked_at": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Unix timestamp when the grant was revoked, null if still active" + } + } + }, + "RegistryMode": { + "type": "string", + "description": "Mode of the registry", + "enum": ["managed", "read_only"] + }, + "RegistryPatchStatus": { + "type": "string", + "description": "Status for registry PATCH (archive only)", + "enum": ["archived"] + }, + "RegistryStatus": { + "type": "string", + "description": "Status of the registry", + "enum": ["creation_pending", "creation_failed", "created"] + }, + "SigningMode": { + "type": "string", + "description": "Signing mode for onchain operations.\n- delegated: ACE signs operations using a delegated key (default; existing behavior).\n- self_sign: ACE creates unsigned operation drafts; the tenant signs and finalizes directly with CREC.\nImmutable after org creation.\n", + "enum": ["delegated", "self_sign"] + }, + "Target": { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "title": { + "type": "string", + "description": "Human-readable title of the target" + }, + "description": { + "type": "string", + "description": "Human-readable description of the target" + }, + "policy_engine_id": { + "$ref": "#/components/schemas/Id" + }, + "protected_methods": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Protected methods of the target" + }, + "onchain_targets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainTarget" + } + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "desired_default_allow": { + "type": "boolean", + "description": "Whether to allow transactions by default if no policy explicitly returns an Allow or Reject. Defaults to true.", + "default": true + }, + "metadata": { + "type": "object", + "description": "JSON metadata associated with the target", + "additionalProperties": true + }, + "archived_at": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Unix timestamp when archived; null if not archived" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + }, + "owner_org_id": { + "$ref": "#/components/schemas/OrgId", + "description": "Owning organization. Present when include_granted=true; for granted targets this is the grantor org." + }, + "access_type": { + "type": "string", + "enum": ["owned", "granted"], + "description": "Whether the caller owns this target or has been granted evaluation access. Only present when include_granted=true." + } + }, + "required": [ + "id", + "title", + "description", + "policy_engine_id", + "protected_methods", + "desired_default_allow", + "chain_selectors", + "created_at", + "updated_at" + ] + }, + "TargetPatchStatus": { + "type": "string", + "description": "Status for target PATCH (archive only)", + "enum": ["archived"] + }, + "TargetProtection": { + "type": "object", + "required": [ + "id", + "function_selector", + "target_id", + "policy_instance_id", + "chain_selectors", + "extractor_output_ids", + "created_at", + "updated_at" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "function_signature": { + "type": "string", + "description": "Function signature of the target protection" + }, + "function_selector": { + "type": "string", + "description": "Function selector of the target protection" + }, + "target_id": { + "$ref": "#/components/schemas/Id" + }, + "policy_instance_id": { + "$ref": "#/components/schemas/Id" + }, + "desired_position": { + "type": "integer", + "description": "Desired position of the target protection" + }, + "onchain_target_protections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainTargetProtection" + } + }, + "chain_selectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + "extractor_output_ids": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + }, + "archived_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp when archived (null if not archived)", + "nullable": true + } + } + }, + "TargetProtectionPatchStatus": { + "type": "string", + "description": "Status for target protection PATCH (archive only)", + "enum": ["archived"] + }, + "TargetProtectionStatus": { + "type": "string", + "description": "Status of the target protection", + "enum": ["creation_pending", "creation_failed", "created", "removal_pending", "removal_failed", "removed"] + }, + "UpdateCredentialRequest": { + "type": "object", + "required": ["external_unique_id"], + "properties": { + "external_unique_id": { + "type": "string", + "description": "External unique identifier for the credential" + }, + "expires_at": { + "type": "integer", + "format": "int64", + "description": "Expiration timestamp of the credential" + } + } + }, + "UpdateCredentialTypeRequest": { + "type": "object", + "required": ["title"], + "properties": { + "title": { + "type": "string", + "description": "Human-readable title of the credential type" + }, + "description": { + "type": "string", + "description": "Human-readable description of the credential type" + } + } + }, + "UpdateDataValidatorRequest": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "onchain_data_validators": { + "type": "array", + "description": "On-chain deployments; each chain_selector may appear at most once", + "items": { + "$ref": "#/components/schemas/CreateOnchainDataValidatorRequest" + } + } + } + }, + "UpdateExtractorRequest": { + "type": "object", + "required": ["name", "supported_function_signatures", "outputs"], + "properties": { + "name": { + "type": "string", + "description": "Human-readable name of the extractor" + }, + "supported_function_signatures": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Function signatures that the extractor supports" + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtractorOutputInput" + } + }, + "onchain_extractors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainExtractorInput" + } + } + } + }, + "UpdateIdentityRequest": { + "type": "object", + "required": ["title", "onchain_identities"], + "properties": { + "title": { + "type": "string", + "description": "Human-readable title of the identity" + }, + "description": { + "type": "string", + "description": "Description of the identity" + }, + "onchain_identities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainIdentityInput" + }, + "description": "Onchain identities to register with the identity" + } + } + }, + "UpdateOffchainPolicyConfigRequest": { + "type": "object", + "required": ["config"], + "properties": { + "config": { + "type": "object", + "additionalProperties": true, + "description": "Updated policy configuration. Triggers redeployment of the workflow with the new config (CRE upserts by owner + name). The policy's deployment_status will transition to config_updating while the operation is in progress.\n" + } + } + }, + "UpdateOnchainPolicyRequest": { + "type": "object", + "properties": {} + }, + "UpdatePolicyEngineRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Human-readable name of the policy engine" + }, + "description": { + "type": "string", + "description": "Human-readable description of the policy engine" + }, + "extractor_ids": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + }, + "onchain_policy_engines": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainPolicyEngineInput" + } + } + }, + "required": ["name", "onchain_policy_engines"] + }, + "UpdatePolicyImplementationRequest": { + "type": "object", + "required": ["name", "description", "onchain_policy_implementations"], + "properties": { + "name": { + "type": "string", + "description": "Human-readable name of the policy implementation" + }, + "description": { + "type": "string", + "description": "Human-readable description of the policy implementation" + }, + "onchain_policy_implementations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OnchainPolicyImplementation" + } + } + } + }, + "UpdatePolicyRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Human-readable name of the policy instance" + }, + "description": { + "type": "string", + "description": "Description of the policy instance" + }, + "onchain_policies": { + "type": "array", + "description": "On-chain deployments; each chain_selector may appear at most once", + "items": { + "$ref": "#/components/schemas/CreateOnchainPolicyRequest" + } + } + } + }, + "UpdateRegistryRequest": { + "type": "object", + "required": ["name", "description"], + "properties": { + "name": { + "type": "string", + "description": "Name of the registry" + }, + "description": { + "type": "string", + "description": "Description of the registry" + }, + "identity_registries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateIdentityRegistryInput" + }, + "description": "List of identity registries" + }, + "credential_registries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateCredentialRegistryInput" + }, + "description": "List of credential registries" + } + } + }, + "UpdateTargetProtectionRequest": { + "type": "object", + "required": ["onchain_target_protections"], + "properties": { + "onchain_target_protections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreateOnchainTargetProtectionRequest" + } + } + } + }, + "UpdateTargetRequest": { + "type": "object", + "required": ["title"], + "properties": { + "owner": { + "$ref": "#/components/schemas/EthAddress" + }, + "title": { + "type": "string", + "description": "Human-readable title of the target" + }, + "description": { + "type": "string", + "description": "Human-readable description of the target" + }, + "protected_methods": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Protected methods of the target" + }, + "onchain_targets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RegisterOnchainTargetRequest" + } + }, + "desired_default_allow": { + "type": "boolean", + "description": "Whether to allow transactions by default if no policy explicitly returns an Allow or Reject. When omitted, the existing value is preserved." + }, + "metadata": { + "type": "object", + "description": "JSON metadata associated with the target", + "additionalProperties": true + } + } + }, + "UpdatedAt": { + "type": "integer", + "format": "int64", + "description": "Timestamp of the last update" + }, + "UserOrganization": { + "type": "object", + "required": [ + "id", + "crec_channel_id", + "crec_channel_offset", + "signing_mode", + "mainnet_allowed", + "created_at", + "updated_at" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/OrgId" + }, + "crec_channel_id": { + "$ref": "#/components/schemas/Id" + }, + "crec_channel_offset": { + "type": "integer", + "description": "Offset of the CREC channel" + }, + "signing_mode": { + "$ref": "#/components/schemas/SigningMode" + }, + "mainnet_allowed": { + "type": "boolean", + "description": "When true, the organization may use mainnet networks for on-chain resources." + }, + "archived_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp when archived (null if active)", + "nullable": true + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + } + }, + "Wallet": { + "type": "object", + "required": [ + "id", + "org_id", + "chain_selector", + "owner_address", + "wallet_address", + "status", + "created_at", + "updated_at" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "owner_address": { + "$ref": "#/components/schemas/EthAddress" + }, + "wallet_address": { + "$ref": "#/components/schemas/EthAddress" + }, + "status": { + "$ref": "#/components/schemas/WalletStatus" + }, + "created_at": { + "$ref": "#/components/schemas/CreatedAt" + }, + "updated_at": { + "$ref": "#/components/schemas/UpdatedAt" + } + } + }, + "WalletStatus": { + "type": "string", + "description": "Status of the wallet", + "enum": ["creation_pending", "creation_failed", "created"] + } + }, + "parameters": { + "include_onchains": { + "name": "include_onchains", + "in": "query", + "description": "Whether to include onchain entities in the response. Defaults to true.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + "page": { + "name": "page", + "in": "query", + "description": "Page number", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + } + }, + "page_size": { + "name": "page_size", + "in": "query", + "description": "Number of items to return per page", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 10 + } + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "Authorization", + "description": "API key authentication. Format: `Apikey `" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + { + "name": "Health Check" + }, + { + "name": "Organizations" + }, + { + "name": "Networks" + }, + { + "name": "Wallets" + }, + { + "name": "Registries" + }, + { + "name": "Identities" + }, + { + "name": "Credential Types" + }, + { + "name": "Credentials" + }, + { + "name": "Policy Engines" + }, + { + "name": "Extractors" + }, + { + "name": "Policy Implementations" + }, + { + "name": "Policies" + }, + { + "name": "Data Validators" + }, + { + "name": "Targets" + }, + { + "name": "Target Protections" + } + ], + "x-constants": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "address": "0x1234567890123456789012345678901234567890", + "chain_id": "42161", + "timestamp": 1700000000, + "chain_family_evm": "evm", + "eth_sepolia_name": "Ethereum Sepolia", + "eth_sepolia_chain_id": "11155111", + "eth_sepolia_chain_selector": "16015286601757825753", + "arbitrum_chain_selector": "4949039107694359620", + "error_bad_request": { + "error": "Bad request", + "message": "Invalid request parameters" + }, + "error_not_found": { + "error": "Not found", + "message": "Resource not found" + }, + "error_conflict": { + "error": "Already exists", + "message": "Resource already exists" + }, + "error_forbidden": { + "error": "Forbidden", + "message": "You do not have permission to access this resource" + }, + "policy_config_schema_example": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "allowlist": { + "type": "array", + "description": "Ethereum addresses permitted to send transactions protected by this policy.", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Ethereum address to allow." + } + }, + "required": ["address"] + }, + "metadata": { + "primary_key_fields": ["address"], + "on_chain_operations": [ + { + "type": "add", + "function_abi": { + "name": "allowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + }, + { + "type": "remove", + "function_abi": { + "name": "disallowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + } + ] + } + } + }, + "policy_run_parameters": [ + { + "name": "Account", + "type": "address", + "max": -1 + } + ], + "initial_configs": [] + } + }, + "paths": { + "/health-check": { + "get": { + "tags": ["Health Check"], + "summary": "Health check endpoint", + "security": [], + "responses": { + "200": { + "description": "Service is healthy", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthCheck" + }, + "example": { + "status": "ok" + } + } + } + } + } + } + }, + "/organizations/me": { + "get": { + "tags": ["Organizations"], + "summary": "Returns the organization the caller belongs to.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserOrganization" + }, + "example": { + "id": "org-123", + "crec_channel_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "crec_channel_offset": 0, + "signing_mode": "delegated", + "mainnet_allowed": false, + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + } + } + } + }, + "/policy-engines": { + "get": { + "tags": ["Policy Engines"], + "summary": "Lists all policy engines.", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "$ref": "#/components/parameters/include_onchains" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListPolicyEnginesResponse" + }, + "example": { + "total": 1, + "total_pages": 1, + "page": 1, + "policy_engines": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy Engine A", + "description": "Description for Policy Engine A", + "type": "standard", + "chain_selectors": ["16015286601757825753"], + "onchain_policy_engines": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "status": "created" + } + ], + "extractor_registrations": [], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + } + } + }, + "post": { + "tags": ["Policy Engines"], + "summary": "Creates a new policy engine.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreatePolicyEngineRequest" + }, + "example": { + "name": "Policy Engine A", + "description": "Description for Policy Engine A", + "extractor_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"], + "onchain_policy_engines": [ + { + "chain_selector": "16015286601757825753" + }, + { + "chain_selector": "4949039107694359620" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PolicyEngine" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy Engine A", + "description": "Description for Policy Engine A", + "type": "standard", + "chain_selectors": ["16015286601757825753", "4949039107694359620"], + "onchain_policy_engines": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "status": "created" + } + ], + "extractor_registrations": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "onchain_extractor_registrations": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "status": "active", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "409": { + "description": "Policy engine already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/policy-engines/{policy_engine_id}": { + "get": { + "tags": ["Policy Engines"], + "summary": "Gets a policy engine by ID.", + "parameters": [ + { + "in": "path", + "name": "policy_engine_id", + "description": "Policy engine ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PolicyEngine" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy Engine A", + "description": "Description for Policy Engine A", + "type": "standard", + "chain_selectors": ["16015286601757825753"], + "onchain_policy_engines": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "status": "created" + } + ], + "extractor_registrations": [], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "404": { + "description": "Policy engine not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "put": { + "tags": ["Policy Engines"], + "summary": "Updates a policy engine. The system policy engine (type \"system\") cannot be updated; it is reserved for registry protection.", + "parameters": [ + { + "in": "path", + "name": "policy_engine_id", + "description": "Policy engine ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdatePolicyEngineRequest" + }, + "example": { + "name": "Policy Engine A Updated", + "description": "Updated description for Policy Engine A", + "extractor_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"], + "onchain_policy_engines": [ + { + "chain_selector": "16015286601757825753" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PolicyEngine" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy Engine A Updated", + "description": "Updated description for Policy Engine A", + "type": "standard", + "chain_selectors": ["16015286601757825753"], + "onchain_policy_engines": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "status": "created" + } + ], + "extractor_registrations": [], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Policy engine not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "patch": { + "tags": ["Policy Engines"], + "summary": "Archives a policy engine. Rejected if already archived, if policy instances exist that are not archived, or if this is the system policy engine reserved for registry protection.", + "parameters": [ + { + "in": "path", + "name": "policy_engine_id", + "description": "Policy engine ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchPolicyEngineRequest" + }, + "example": { + "status": "archived" + } + } + } + }, + "responses": { + "202": { + "description": "Request accepted. Returns policy engine when archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PolicyEngine" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy Engine A", + "description": "Description for Policy Engine A", + "type": "standard", + "chain_selectors": ["16015286601757825753"], + "onchain_policy_engines": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "status": "created" + } + ], + "extractor_registrations": [], + "archived_at": 1700000000, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. already archived, or policy instances exist that are not archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Policy engine not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + } + }, + "/extractors": { + "get": { + "tags": ["Extractors"], + "summary": "Lists extractors.", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "$ref": "#/components/parameters/include_onchains" + }, + { + "in": "query", + "name": "policy_engine_id", + "description": "Policy engine ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": false + }, + { + "in": "query", + "name": "function_signature", + "description": "Function signature", + "schema": { + "type": "string" + }, + "required": false + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListExtractorsResponse" + }, + "example": { + "total": 1, + "total_pages": 1, + "page": 1, + "extractors": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Extractor A", + "org_id": "org-123", + "supported_function_signatures": ["mint(address,uint256)", "burn(address,uint256)"], + "chain_selectors": ["16015286601757825753"], + "outputs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "account", + "type": "address", + "created_at": 1700000000, + "updated_at": 1700000000 + }, + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "amount", + "type": "uint256", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "onchain_extractors": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + } + } + }, + "post": { + "tags": ["Extractors"], + "summary": "Creates a new extractor.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateExtractorRequest" + }, + "example": { + "name": "Extractor A", + "supported_function_signatures": ["mint(address,uint256)", "burn(address,uint256)"], + "outputs": [ + { + "name": "account", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "onchain_extractors": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Extractor" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Extractor A", + "org_id": "org-123", + "supported_function_signatures": ["mint(address,uint256)", "burn(address,uint256)"], + "chain_selectors": ["16015286601757825753"], + "outputs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "account", + "type": "address", + "created_at": 1700000000, + "updated_at": 1700000000 + }, + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "amount", + "type": "uint256", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "onchain_extractors": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + } + } + } + }, + "/extractors/{extractor_id}": { + "get": { + "tags": ["Extractors"], + "summary": "Gets an extractor by ID.", + "parameters": [ + { + "in": "path", + "name": "extractor_id", + "description": "Extractor ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Extractor" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Extractor A", + "org_id": "org-123", + "supported_function_signatures": ["mint(address,uint256)", "burn(address,uint256)"], + "chain_selectors": ["16015286601757825753"], + "outputs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "account", + "type": "address", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "onchain_extractors": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "404": { + "description": "Extractor not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "put": { + "tags": ["Extractors"], + "summary": "Updates an extractor.", + "parameters": [ + { + "in": "path", + "name": "extractor_id", + "description": "Extractor ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateExtractorRequest" + }, + "example": { + "name": "Extractor A", + "supported_function_signatures": ["mint(address,uint256)", "burn(address,uint256)"], + "outputs": [ + { + "name": "account", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "onchain_extractors": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Extractor" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Extractor A", + "org_id": "org-123", + "supported_function_signatures": ["mint(address,uint256)", "burn(address,uint256)"], + "chain_selectors": ["16015286601757825753"], + "outputs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "account", + "type": "address", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "onchain_extractors": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Extractor not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "patch": { + "tags": ["Extractors"], + "summary": "Archives an extractor. Rejected if already archived.", + "parameters": [ + { + "in": "path", + "name": "extractor_id", + "description": "Extractor ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchExtractorRequest" + }, + "example": { + "status": "archived" + } + } + } + }, + "responses": { + "202": { + "description": "Request accepted. Returns extractor when archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Extractor" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Extractor A", + "org_id": "org-123", + "supported_function_signatures": ["mint(address,uint256)", "burn(address,uint256)"], + "chain_selectors": ["16015286601757825753"], + "outputs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "account", + "type": "address", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "onchain_extractors": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "extractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": 1700000000, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. already archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Extractor not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + } + }, + "/policy-implementations": { + "get": { + "tags": ["Policy Implementations"], + "summary": "Lists all policy implementations (onchain and offchain).", + "description": "When `policy_kind` is omitted, returns onchain implementations only (backward compatible with existing clients). Use `policy_kind=all` to return both onchain and offchain implementations merged in a single list.\n", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "$ref": "#/components/parameters/include_onchains" + }, + { + "in": "query", + "name": "policy_kind", + "description": "Filter by policy kind. Omit or set to `onchain` for onchain-only (default). Set to `offchain` for offchain-only. Set to `all` for both kinds merged.\n", + "schema": { + "type": "string", + "enum": ["onchain", "offchain", "all"] + }, + "required": false + }, + { + "in": "query", + "name": "name", + "description": "Name", + "schema": { + "type": "string" + }, + "required": false + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListPolicyImplementationsResponse" + }, + "example": { + "total": 1, + "total_pages": 1, + "page": 1, + "implementations": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy Implementation A", + "description": "Description for Policy Implementation A", + "policy_config_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "allowlist": { + "type": "array", + "description": "Ethereum addresses permitted to send transactions protected by this policy.", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Ethereum address to allow." + } + }, + "required": ["address"] + }, + "metadata": { + "primary_key_fields": ["address"], + "on_chain_operations": [ + { + "type": "add", + "function_abi": { + "name": "allowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + }, + { + "type": "remove", + "function_abi": { + "name": "disallowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + } + ] + } + } + }, + "policy_run_parameters": [ + { + "name": "Account", + "type": "address", + "max": -1 + } + ], + "initial_configs": [] + }, + "chain_selectors": ["16015286601757825753"], + "onchain_policy_implementations": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ], + "parameters": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Account", + "type": "address", + "position": 0, + "max": -1, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + } + } + }, + "post": { + "tags": ["Policy Implementations"], + "summary": "Register a custom policy implementation.", + "description": "Registers a new policy implementation by supplying its name, description, on-chain addresses, and a policy_config_schema that declares the policy's configurable parameters.\n\nThe policy_config_schema is a JSON Schema document (draft-07) extended with ACE-specific fields:\n- **properties** — one entry per configurable parameter (e.g. an allowlist or a threshold). Each entry requires type, description, and a metadata block.\n- **metadata.on_chain_operations** — maps each type of config change (add, remove, replace) to a Solidity setter function. Copy the function_abi from your contract's JSON ABI build artifact.\n- **policy_run_parameters** — transaction values extracted by the policy engine and passed to the contract's run() function at enforcement time. Use [] if none.\n- **initial_configs** — ordered list of parameter names that must be set at deploy time (matching the configure() argument order). Use [] if none.\n- **$schema**, **type**, **additionalProperties** — always the fixed values shown in the example below.\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterPolicyImplementationRequest" + }, + "example": { + "name": "Allow Policy", + "description": "Maintains an allowlist. Rejects the transaction if any checked address is not on the list.", + "policy_config_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "allowlist": { + "type": "array", + "description": "Ethereum addresses permitted to send transactions protected by this policy.", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Ethereum address to allow." + } + }, + "required": ["address"] + }, + "metadata": { + "display_hints": { + "network_behaviour": "apply_per_chain", + "title": "Allowed addresses" + }, + "primary_key_fields": ["address"], + "on_chain_operations": [ + { + "type": "add", + "function_abi": { + "name": "allowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + }, + { + "type": "remove", + "function_abi": { + "name": "disallowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + } + ] + } + } + }, + "policy_run_parameters": [ + { + "name": "Account", + "type": "address", + "max": -1 + } + ], + "initial_configs": [] + }, + "onchain_policy_implementations": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PolicyImplementation" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy Implementation A", + "description": "Description for Policy Implementation A", + "policy_config_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "allowlist": { + "type": "array", + "description": "Ethereum addresses permitted to send transactions protected by this policy.", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Ethereum address to allow." + } + }, + "required": ["address"] + }, + "metadata": { + "primary_key_fields": ["address"], + "on_chain_operations": [ + { + "type": "add", + "function_abi": { + "name": "allowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + }, + { + "type": "remove", + "function_abi": { + "name": "disallowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + } + ] + } + } + }, + "policy_run_parameters": [ + { + "name": "Account", + "type": "address", + "max": -1 + } + ], + "initial_configs": [] + }, + "chain_selectors": ["16015286601757825753"], + "onchain_policy_implementations": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ], + "parameters": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Account", + "type": "address", + "position": 0, + "max": -1, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "409": { + "description": "Policy implementation already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/policy-implementations/{policy_implementation_id}": { + "get": { + "tags": ["Policy Implementations"], + "summary": "Gets a policy implementation by ID.", + "parameters": [ + { + "in": "path", + "name": "policy_implementation_id", + "description": "Policy implementation ID", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnyPolicyImplementationResponse" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy Implementation A", + "description": "Description for Policy Implementation A", + "policy_config_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "allowlist": { + "type": "array", + "description": "Ethereum addresses permitted to send transactions protected by this policy.", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Ethereum address to allow." + } + }, + "required": ["address"] + }, + "metadata": { + "primary_key_fields": ["address"], + "on_chain_operations": [ + { + "type": "add", + "function_abi": { + "name": "allowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + }, + { + "type": "remove", + "function_abi": { + "name": "disallowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + } + ] + } + } + }, + "policy_run_parameters": [ + { + "name": "Account", + "type": "address", + "max": -1 + } + ], + "initial_configs": [] + }, + "chain_selectors": ["16015286601757825753"], + "onchain_policy_implementations": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ], + "parameters": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Account", + "type": "address", + "position": 0, + "max": -1, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "404": { + "description": "Policy implementation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "patch": { + "tags": ["Policy Implementations"], + "summary": "Archives a policy implementation. Rejected if policy instances exist.", + "parameters": [ + { + "in": "path", + "name": "policy_implementation_id", + "description": "Policy implementation ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchPolicyImplementationRequest" + }, + "example": { + "status": "archived" + } + } + } + }, + "responses": { + "202": { + "description": "Request accepted. Returns policy implementation when archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PolicyImplementation" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy Implementation A", + "description": "Description for Policy Implementation A", + "policy_config_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "allowlist": { + "type": "array", + "description": "Ethereum addresses permitted to send transactions protected by this policy.", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Ethereum address to allow." + } + }, + "required": ["address"] + }, + "metadata": { + "primary_key_fields": ["address"], + "on_chain_operations": [ + { + "type": "add", + "function_abi": { + "name": "allowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + }, + { + "type": "remove", + "function_abi": { + "name": "disallowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + } + ] + } + } + }, + "policy_run_parameters": [ + { + "name": "Account", + "type": "address", + "max": -1 + } + ], + "initial_configs": [] + }, + "chain_selectors": ["16015286601757825753"], + "onchain_policy_implementations": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ], + "parameters": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Account", + "type": "address", + "position": 0, + "max": -1, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": 1700000000, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. already archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Policy implementation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + }, + "409": { + "description": "Cannot archive - policy instances exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + }, + "put": { + "tags": ["Policy Implementations"], + "summary": "Update a policy implementation", + "parameters": [ + { + "in": "path", + "name": "policy_implementation_id", + "description": "Policy implementation ID", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdatePolicyImplementationRequest" + }, + "example": { + "name": "Policy Implementation A", + "description": "Updated description for Policy Implementation A", + "onchain_policy_implementations": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PolicyImplementation" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy Implementation A", + "description": "Updated description for Policy Implementation A", + "policy_config_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "allowlist": { + "type": "array", + "description": "Ethereum addresses permitted to send transactions protected by this policy.", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Ethereum address to allow." + } + }, + "required": ["address"] + }, + "metadata": { + "primary_key_fields": ["address"], + "on_chain_operations": [ + { + "type": "add", + "function_abi": { + "name": "allowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + }, + { + "type": "remove", + "function_abi": { + "name": "disallowAddress", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "address", + "type": "address" + } + ], + "outputs": [] + } + } + ] + } + } + }, + "policy_run_parameters": [ + { + "name": "Account", + "type": "address", + "max": -1 + } + ], + "initial_configs": [] + }, + "chain_selectors": ["16015286601757825753"], + "onchain_policy_implementations": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ], + "parameters": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Account", + "type": "address", + "position": 0, + "max": -1, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. already archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Policy implementation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + } + }, + "/targets": { + "get": { + "tags": ["Targets"], + "summary": "Lists all targets.", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "$ref": "#/components/parameters/include_onchains" + }, + { + "in": "query", + "name": "policy_engine_id", + "description": "Policy Engine ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": false + }, + { + "in": "query", + "name": "search", + "description": "Search by target title or onchain address", + "schema": { + "type": "string" + }, + "required": false + }, + { + "in": "query", + "name": "chain_selector", + "description": "Filter by chain selector", + "schema": { + "type": "string" + }, + "required": false + }, + { + "in": "query", + "name": "include_granted", + "description": "When true, include targets owned by other orgs that granted evaluation access to the caller.", + "schema": { + "type": "boolean", + "default": false + }, + "required": false + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListTargetsResponse" + }, + "example": { + "total": 1, + "total_pages": 1, + "page": 1, + "targets": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Target A", + "description": "Description for Target A", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "protected_methods": ["transfer(address,uint256)", "transferFrom(address,address,uint256)"], + "desired_default_allow": true, + "metadata": {}, + "chain_selectors": ["16015286601757825753"], + "onchain_targets": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "default_allow": true + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + } + } + }, + "post": { + "tags": ["Targets"], + "summary": "Registers a new target. Cannot reference the system policy engine (type \"system\"); that engine is reserved for registry protection.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterTargetRequest" + }, + "example": { + "title": "Target A", + "description": "Description for Target A", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "protected_methods": ["transfer(address,uint256)", "transferFrom(address,address,uint256)"], + "desired_default_allow": true, + "metadata": {}, + "onchain_targets": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Target" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Target A", + "description": "Description for Target A", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "protected_methods": ["transfer(address,uint256)", "transferFrom(address,address,uint256)"], + "desired_default_allow": true, + "metadata": {}, + "chain_selectors": ["16015286601757825753"], + "onchain_targets": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "default_allow": true + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "409": { + "description": "Target already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/targets/{target_id}": { + "get": { + "tags": ["Targets"], + "summary": "Gets a target by ID.", + "parameters": [ + { + "in": "path", + "name": "target_id", + "description": "Target ID", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Target" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Target A", + "description": "Description for Target A", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "protected_methods": ["transfer(address,uint256)", "transferFrom(address,address,uint256)"], + "desired_default_allow": true, + "metadata": {}, + "chain_selectors": ["16015286601757825753"], + "onchain_targets": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "default_allow": true + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "404": { + "description": "Target not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "put": { + "tags": ["Targets"], + "summary": "Updates a target. Rejected if the target uses the system policy engine (registry targets are managed internally).", + "parameters": [ + { + "in": "path", + "name": "target_id", + "description": "Target ID", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTargetRequest" + }, + "example": { + "title": "Target A Updated", + "description": "Updated description for Target A", + "protected_methods": [ + "transfer(address,uint256)", + "transferFrom(address,address,uint256)", + "mint(address,uint256)" + ], + "desired_default_allow": true, + "metadata": {}, + "onchain_targets": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Target" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Target A Updated", + "description": "Updated description for Target A", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "protected_methods": [ + "transfer(address,uint256)", + "transferFrom(address,address,uint256)", + "mint(address,uint256)" + ], + "desired_default_allow": true, + "metadata": {}, + "chain_selectors": ["16015286601757825753"], + "onchain_targets": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "default_allow": true + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. already archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Target not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "patch": { + "tags": ["Targets"], + "summary": "Archives a target. Rejected if policy target protections exist that are not archived, or if the target uses the system policy engine.", + "parameters": [ + { + "in": "path", + "name": "target_id", + "description": "Target ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchTargetRequest" + }, + "example": { + "status": "archived" + } + } + } + }, + "responses": { + "202": { + "description": "Request accepted. Returns target when archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Target" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Target A", + "description": "Description for Target A", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "protected_methods": ["transfer(address,uint256)", "transferFrom(address,address,uint256)"], + "desired_default_allow": true, + "metadata": {}, + "chain_selectors": ["16015286601757825753"], + "onchain_targets": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "default_allow": true + } + ], + "archived_at": 1700000000, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. already archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Target not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + }, + "409": { + "description": "Cannot archive - policy target protections exist that are not archived", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/targets/{target_id}/merge": { + "post": { + "tags": ["Targets"], + "summary": "Merges source targets into this target by transferring their onchain targets and archiving the sources. The targets must belong to the same policy engine and must not have onchain targets on the same chain. The source targets will be archived after the merge.", + "parameters": [ + { + "in": "path", + "name": "target_id", + "description": "Destination Target ID (the target that will receive the onchain targets)", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MergeTargetRequest" + }, + "example": { + "source_target_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Target" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Target A", + "description": "Description for Target A", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "protected_methods": ["transfer(address,uint256)", "transferFrom(address,address,uint256)"], + "desired_default_allow": true, + "metadata": {}, + "chain_selectors": ["16015286601757825753"], + "onchain_targets": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "default_allow": true + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. archived target, system engine, same target)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Source or destination target not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + }, + "409": { + "description": "Cannot merge", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/targets/{target_id}/protections": { + "get": { + "tags": ["Target Protections"], + "summary": "Lists all protections for a target.", + "description": "Returns onchain target protections only. Offchain (CADV) protections created via POST /targets/{target_id}/protections with `policy_kind` `\"offchain\"` are managed under the offchain policy and are listed via GET /policies/{policy_id}/protections instead.\n", + "parameters": [ + { + "in": "path", + "name": "target_id", + "description": "Target ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + }, + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "$ref": "#/components/parameters/include_onchains" + }, + { + "in": "query", + "name": "function_signature", + "description": "Function signature", + "schema": { + "type": "string" + }, + "required": false + }, + { + "in": "query", + "name": "policy_instance_id", + "description": "Policy instance ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": false + }, + { + "in": "query", + "name": "status", + "description": "Onchain target protection status", + "schema": { + "$ref": "#/components/schemas/TargetProtectionStatus" + }, + "required": false + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListTargetProtectionsResponse" + }, + "example": { + "total": 1, + "total_pages": 1, + "page": 1, + "target_protections": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "function_signature": "transfer(address,uint256)", + "function_selector": "0xa9059cbb", + "target_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_instance_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "desired_position": 1, + "chain_selectors": ["16015286601757825753"], + "extractor_output_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"], + "onchain_target_protections": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "position": 1, + "status": "created" + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + } + } + }, + "post": { + "tags": ["Target Protections"], + "summary": "Creates a new target protection (onchain or offchain). Rejected if the target uses the system policy engine (registry protections are managed internally).", + "description": "Set `policy_instance_id` to the policy ID to protect. For onchain policies this is the onchain policy instance ID (`policy_kind` may be omitted). For offchain (CADV) policies this is the offchain policy ID and `policy_kind` must be `\"offchain\"`. Onchain protections return 200; offchain protection attachment is queued and returns 202.\n", + "parameters": [ + { + "in": "path", + "name": "target_id", + "description": "Target ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTargetProtectionRequest" + }, + "examples": { + "onchain": { + "summary": "Onchain policy protection", + "value": { + "function_signature": "transfer(address,uint256)", + "policy_instance_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "desired_position": 1, + "onchain_target_protections": [ + { + "chain_selector": "16015286601757825753" + } + ], + "extractor_output_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"] + } + }, + "offchain": { + "summary": "Offchain (CADV) policy protection", + "value": { + "function_signature": "transfer(address,uint256)", + "policy_instance_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_kind": "offchain", + "desired_position": 0, + "extractor_output_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Onchain protection created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TargetProtection" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "function_signature": "transfer(address,uint256)", + "function_selector": "0xa9059cbb", + "target_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_instance_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "desired_position": 1, + "chain_selectors": ["16015286601757825753"], + "extractor_output_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"], + "onchain_target_protections": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "position": 1, + "status": "created" + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "202": { + "description": "Offchain protection attachment accepted (async)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OffchainPolicyProtectionResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Target or policy not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + }, + "409": { + "description": "Conflict - target protection with same policy, target, and function selector already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/targets/{target_id}/protections/{protection_id}": { + "get": { + "tags": ["Target Protections"], + "summary": "Gets a specific protection on a target.", + "parameters": [ + { + "in": "path", + "name": "target_id", + "description": "Target ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + }, + { + "in": "path", + "name": "protection_id", + "description": "Protection ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TargetProtection" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "function_signature": "transfer(address,uint256)", + "function_selector": "0xa9059cbb", + "target_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_instance_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "desired_position": 1, + "chain_selectors": ["16015286601757825753"], + "extractor_output_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"], + "onchain_target_protections": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "position": 1, + "status": "created" + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "404": { + "description": "Target or protection not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "put": { + "tags": ["Target Protections"], + "summary": "Updates a target protection. Rejected if the target uses the system policy engine.", + "parameters": [ + { + "in": "path", + "name": "target_id", + "description": "Target ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + }, + { + "in": "path", + "name": "protection_id", + "description": "Protection ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTargetProtectionRequest" + }, + "example": { + "onchain_target_protections": [ + { + "chain_selector": "16015286601757825753" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TargetProtection" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "function_signature": "transfer(address,uint256)", + "function_selector": "0xa9059cbb", + "target_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_instance_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "desired_position": 1, + "chain_selectors": ["16015286601757825753"], + "extractor_output_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"], + "onchain_target_protections": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "position": 1, + "status": "created" + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. target protection already archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Target or protection not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "patch": { + "tags": ["Target Protections"], + "summary": "Archives a target protection.", + "description": "Pass status=archived to archive. Only archives when all onchain target protections\nhave been removed from chain. If onchain protections still exist, triggers removal\nand archives when all are removed (async).\nRejected if the target uses the system policy engine (registry protections are managed internally).\n", + "parameters": [ + { + "in": "path", + "name": "target_id", + "description": "Target ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + }, + { + "in": "path", + "name": "protection_id", + "description": "Protection ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchTargetProtectionRequest" + }, + "example": { + "status": "archived" + } + } + } + }, + "responses": { + "202": { + "description": "Request accepted. Returns archived target protection.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TargetProtection" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "function_signature": "transfer(address,uint256)", + "function_selector": "0xa9059cbb", + "target_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_instance_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "desired_position": 1, + "chain_selectors": ["16015286601757825753"], + "extractor_output_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"], + "onchain_target_protections": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "position": 1, + "status": "created" + } + ], + "archived_at": 1700000000, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. status must be archived, already archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Target or protection not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + } + }, + "/policies": { + "get": { + "tags": ["Policies"], + "summary": "Lists all policies (onchain and offchain).", + "description": "When `policy_kind` is omitted, returns onchain policies only with standard DB pagination (backward compatible with existing clients). Use `policy_kind=all` to return both onchain and offchain policies merged in a single list. Onchain-only query filters (e.g. `target_id`, `policy_engine_id`) exclude offchain policies from merged results.\n", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "$ref": "#/components/parameters/include_onchains" + }, + { + "in": "query", + "name": "policy_kind", + "description": "Filter by policy kind. Omit or set to `onchain` for onchain-only (default). Set to `offchain` for offchain-only. Set to `all` for both kinds merged.\n", + "schema": { + "type": "string", + "enum": ["onchain", "offchain", "all"] + }, + "required": false + }, + { + "in": "query", + "name": "target_id", + "description": "Filter by target ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": false + }, + { + "in": "query", + "name": "status", + "description": "Filter by policy status", + "schema": { + "$ref": "#/components/schemas/OnchainPolicyInstanceStatus" + }, + "required": false + }, + { + "in": "query", + "name": "name", + "description": "Name", + "schema": { + "type": "string" + }, + "required": false + }, + { + "in": "query", + "name": "only_with_active_protections", + "description": "Only return policies with active protections", + "schema": { + "type": "boolean", + "default": false + }, + "required": false + }, + { + "in": "query", + "name": "target_address", + "description": "Filter by target contract address", + "schema": { + "type": "string" + }, + "required": false + }, + { + "in": "query", + "name": "policy_engine_id", + "description": "Policy engine ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": false + }, + { + "in": "query", + "name": "policy_implementation_id", + "description": "Filter by policy implementation ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": false + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListPoliciesResponse" + }, + "example": { + "total": 2, + "total_pages": 1, + "page": 1, + "policies": [ + { + "policy_kind": "onchain", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy A", + "description": "Description for Policy A", + "policy_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_implementation_name": "Policy Implementation A", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": { + "allowList": ["0x1234567890123456789012345678901234567890"] + } + } + ], + "onchain_policies": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "policy_config_version": "1", + "out_of_sync": false, + "status": "created" + } + ], + "ongoing_config_changes": [], + "archived_at": null + }, + { + "policy_kind": "offchain", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "type": "wallet_risk_scoring", + "name": "Transaction wallet screening", + "config": { + "secret_name": "trmApiKey", + "addresses_to_check": "ALL", + "fail_mode": "CLOSED", + "risk_threshold": "HIGH", + "block_unknown": false, + "category_filters": [{ "category": "Sanctions", "threshold": "LOW" }] + }, + "chain_selectors": ["16015286601757825753"], + "deployment_status": "active", + "created_at": "2024-01-01T00:00:00Z" + } + ] + } + } + } + } + } + }, + "post": { + "tags": ["Policies"], + "summary": "Creates a new policy (onchain or offchain). Use `policy_kind` to select the type. Policies cannot attach to the system policy engine (type \"system\"); that engine is reserved for registry protection.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAnyPolicyRequest" + }, + "examples": { + "onchain": { + "summary": "Create an onchain policy", + "value": { + "policy_kind": "onchain", + "name": "Policy A", + "description": "Description for Policy A", + "policy_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "onchain_policies": [ + { + "chain_selector": "16015286601757825753", + "initial_config": { + "allowList": ["0x1234567890123456789012345678901234567890"] + } + } + ] + } + }, + "offchain": { + "summary": "Create an offchain policy", + "value": { + "policy_kind": "offchain", + "type": "wallet_risk_scoring", + "name": "Transaction wallet screening", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "onchain_policies": [ + { + "chain_selector": "16015286601757825753" + } + ], + "config": { + "secret_name": "trmApiKey", + "addresses_to_check": "ALL", + "fail_mode": "CLOSED", + "risk_threshold": "HIGH", + "block_unknown": false, + "category_filters": [{ "category": "Sanctions", "threshold": "LOW" }] + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnyPolicyResponse" + }, + "examples": { + "onchain": { + "summary": "Onchain policy created", + "value": { + "policy_kind": "onchain", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy A", + "description": "Description for Policy A", + "policy_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": { + "allowList": ["0x1234567890123456789012345678901234567890"] + } + } + ], + "onchain_policies": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "policy_config_version": "1", + "out_of_sync": false, + "status": "created" + } + ], + "ongoing_config_changes": [], + "archived_at": null + } + }, + "offchain": { + "summary": "Offchain policy created", + "value": { + "policy_kind": "offchain", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "type": "wallet_risk_scoring", + "name": "Transaction wallet screening", + "config": { + "secret_name": "trmApiKey", + "addresses_to_check": "ALL", + "fail_mode": "CLOSED", + "risk_threshold": "HIGH", + "block_unknown": false, + "category_filters": [{ "category": "Sanctions", "threshold": "LOW" }] + }, + "action_validators": [ + { + "chain_selector": "16015286601757825753", + "validator_address": "" + } + ], + "deployment_status": "pending", + "created_at": "2024-01-01T00:00:00Z" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Policy implementation or policy engine not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + }, + "409": { + "description": "Conflict (e.g. active offchain policy already exists for this org)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + } + } + } + }, + "/policies/{policy_id}": { + "get": { + "tags": ["Policies"], + "summary": "Gets a policy by ID (onchain or offchain).", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "description": "Policy ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnyPolicyResponse" + }, + "examples": { + "onchain": { + "summary": "Onchain policy", + "value": { + "policy_kind": "onchain", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy A", + "description": "Description for Policy A", + "policy_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": { + "allowList": ["0x1234567890123456789012345678901234567890"] + } + } + ], + "onchain_policies": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "policy_config_version": "1", + "out_of_sync": false, + "status": "created" + } + ], + "ongoing_config_changes": [], + "archived_at": null + } + }, + "offchain": { + "summary": "Offchain policy", + "value": { + "policy_kind": "offchain", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "type": "wallet_risk_scoring", + "name": "Transaction wallet screening", + "config": { + "secret_name": "trmApiKey", + "addresses_to_check": "ALL", + "fail_mode": "CLOSED", + "risk_threshold": "HIGH", + "block_unknown": false, + "category_filters": [{ "category": "Sanctions", "threshold": "LOW" }] + }, + "chain_selectors": ["16015286601757825753"], + "deployment_status": "active", + "created_at": "2024-01-01T00:00:00Z" + } + } + } + } + } + }, + "404": { + "description": "Policy not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "put": { + "tags": ["Policies"], + "summary": "Updates a policy. Rejected if the policy is on the system policy engine reserved for registry protection.", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "description": "Policy ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdatePolicyRequest" + }, + "example": { + "name": "Policy A Updated", + "description": "Updated description for Policy A", + "onchain_policies": [ + { + "chain_selector": "16015286601757825753" + }, + { + "chain_selector": "4949039107694359620" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Policy" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy A Updated", + "description": "Updated description for Policy A", + "policy_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_implementation_name": "Policy Implementation A", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753", "4949039107694359620"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": { + "allowList": ["0x1234567890123456789012345678901234567890"] + } + } + ], + "onchain_policies": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "policy_config_version": "1", + "out_of_sync": false, + "status": "created" + } + ], + "ongoing_config_changes": [], + "archived_at": null + } + } + } + }, + "400": { + "description": "Bad request (e.g. policy already archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Policy not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "patch": { + "tags": ["Policies"], + "summary": "Archives a policy (onchain or offchain). For onchain policies, rejected if target protections exist that are not archived or if the policy is on the system policy engine. For offchain policies, triggers workflow undeployment.", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "required": true, + "description": "Policy ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchPolicyRequest" + }, + "examples": { + "onchain": { + "summary": "Archive an onchain policy", + "value": { + "status": "archived" + } + }, + "offchain": { + "summary": "Archive an offchain policy (triggers workflow undeployment)", + "value": { + "status": "archived", + "policy_kind": "offchain" + } + } + } + } + } + }, + "responses": { + "202": { + "description": "Request accepted. Returns the archived policy.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnyPolicyResponse" + }, + "examples": { + "onchain": { + "value": { + "policy_kind": "onchain", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy A", + "description": "Description for Policy A", + "policy_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": { + "allowList": ["0x1234567890123456789012345678901234567890"] + } + } + ], + "onchain_policies": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "policy_config_version": "1", + "out_of_sync": false, + "status": "created" + } + ], + "ongoing_config_changes": [], + "archived_at": 1700000000 + } + }, + "offchain": { + "value": { + "policy_kind": "offchain", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Transaction wallet screening", + "type": "wallet_risk_scoring", + "action_validators": [ + { + "chain_selector": "16015286601757825753", + "validator_address": "0x1234567890123456789012345678901234567890" + } + ], + "deployment_status": "removing", + "created_at": 1700000000 + } + } + } + } + } + }, + "400": { + "description": "Bad request (e.g. already archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Policy not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + }, + "409": { + "description": "Cannot archive - target protections exist that are not archived", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/policies/{policy_id}/protections": { + "get": { + "tags": ["Policies"], + "summary": "List target protections for an offchain policy.", + "description": "Returns protections attached to the given offchain policy. Only applicable to offchain policies; onchain policy protections are listed via GET /targets/{target_id}/protections.\n", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "required": true, + "description": "Offchain policy ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListOffchainPolicyProtectionsResponse" + } + } + } + }, + "404": { + "description": "Offchain policy not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + } + } + } + }, + "/policies/{policy_id}/protections/{protection_id}": { + "get": { + "tags": ["Policies"], + "summary": "Get a specific offchain policy protection.", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "in": "path", + "name": "protection_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OffchainPolicyProtectionResponse" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + } + } + }, + "delete": { + "tags": ["Policies"], + "summary": "Remove an offchain policy protection (detaches validator from function on engine).", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "in": "path", + "name": "protection_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "responses": { + "202": { + "description": "Protection removal accepted" + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + } + } + } + }, + "/policies/{policy_id}/config": { + "put": { + "tags": ["Policies"], + "summary": "Update the config of an offchain policy. Config is immutable once deployed; calling this endpoint triggers redeployment of the workflow with the new config (CRE upserts by owner + name). The policy's deployment_status transitions to config_updating while the operation is in progress, then back to active on success.\n", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "required": true, + "description": "Offchain policy ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateOffchainPolicyConfigRequest" + } + } + } + }, + "responses": { + "202": { + "description": "Config update accepted; redeployment queued", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnyPolicyResponse" + } + } + } + }, + "400": { + "description": "Bad request (invalid config or policy not in updatable state)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "404": { + "description": "Policy not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + } + } + } + }, + "/policies/{policy_id}/targets/{target_id}/access-grants": { + "get": { + "tags": ["Policies"], + "summary": "List evaluation access grants for an offchain policy and target pair.", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "required": true, + "description": "Offchain policy ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "name": "target_id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListRegistryAccessGrantsResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": ["Policies"], + "summary": "Grant another organization evaluation access for an offchain policy on a target.", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "required": true, + "description": "Offchain policy ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "name": "target_id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateRegistryAccessGrantRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Grant created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegistryAccessGrant" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "409": { + "description": "Active grant already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + } + } + } + }, + "/policies/{policy_id}/targets/{target_id}/access-grants/{grantee_org_id}": { + "patch": { + "tags": ["Policies"], + "summary": "Revoke evaluation access for a grantee organization.", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "required": true, + "description": "Offchain policy ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "name": "target_id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "name": "grantee_org_id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/OrgId" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchRegistryAccessGrantRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Grant revoked", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegistryAccessGrant" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + } + } + } + }, + "/policies/{policy_id}/onchain/{onchain_policy_id}": { + "put": { + "tags": ["Policies"], + "summary": "Updates states of an onchain policy.", + "description": "This endpoint triggers a re-sync of the onchain policy state. The request body is empty.\n", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "description": "Policy ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + }, + { + "in": "path", + "name": "onchain_policy_id", + "description": "Onchain policy ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateOnchainPolicyRequest" + }, + "example": {} + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OnchainPolicyInstance" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "policy_config_version": "1", + "out_of_sync": false, + "status": "created" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Policy or onchain policy not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + } + }, + "/policies/{policy_id}/configs": { + "patch": { + "tags": ["Policies"], + "summary": "Updates policy configurations with JSON patches.", + "description": "Applies [JSON Patch (RFC 6902)](https://jsonpatch.com/) operations\nto the policy's configuration.\n", + "parameters": [ + { + "in": "path", + "name": "policy_id", + "description": "Policy ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchPolicyConfigsRequest" + }, + "example": { + "patches": [ + { + "op": "add", + "path": "/allowList/-", + "value": "0x1234567890123456789012345678901234567890" + }, + { + "op": "remove", + "path": "/allowList/0" + } + ], + "apply_onchain_policies": [ + { + "chain_selector": "16015286601757825753", + "current_policy_config_version": "1" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Policy" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Policy A", + "description": "Description for Policy A", + "policy_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "policy_implementation_name": "Policy Implementation A", + "policy_engine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": { + "allowList": ["0x1234567890123456789012345678901234567890"] + } + } + ], + "onchain_policies": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "policy_config_version": "2", + "out_of_sync": false, + "status": "created" + } + ], + "ongoing_config_changes": [], + "archived_at": null + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Policy not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + } + }, + "/data-validators": { + "get": { + "tags": ["Data Validators"], + "summary": "Lists all data validator instances.", + "description": "Optional filters combine with AND semantics. Use `chain_selector` and/or `address` to narrow\nlist to instances that have a matching on-chain deployment row.\n", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "$ref": "#/components/parameters/include_onchains" + }, + { + "in": "query", + "name": "status", + "description": "Filters instances that have an on-chain deployment in this status", + "schema": { + "$ref": "#/components/schemas/OnchainDataValidatorInstanceStatus" + }, + "required": false + }, + { + "in": "query", + "name": "name", + "description": "Name (case-insensitive substring)", + "schema": { + "type": "string" + }, + "required": false + }, + { + "in": "query", + "name": "data_validator_implementation_id", + "description": "Data validator implementation ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": false + }, + { + "in": "query", + "name": "chain_selector", + "description": "When set, only instances that have an on-chain deployment on this CCIP chain selector are returned", + "schema": { + "$ref": "#/components/schemas/ChainSelector" + }, + "required": false + }, + { + "in": "query", + "name": "address", + "description": "When set, only instances that have an on-chain deployment at this contract address are returned (case-insensitive match)", + "schema": { + "$ref": "#/components/schemas/EthAddress" + }, + "required": false + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListDataValidatorsResponse" + }, + "example": { + "total": 1, + "total_pages": 1, + "page": 1, + "data_validators": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Data Validator A", + "description": "Description", + "data_validator_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": {} + } + ], + "onchain_data_validators": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "data_validator_config_version": "0", + "status": "created" + } + ], + "ongoing_config_changes": [], + "archived_at": null + } + ] + } + } + } + } + } + }, + "post": { + "tags": ["Data Validators"], + "summary": "Creates a new data validator instance.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDataValidatorRequest" + }, + "example": { + "name": "Data Validator A", + "description": "Description", + "data_validator_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "onchain_data_validators": [ + { + "chain_selector": "16015286601757825753", + "initial_config": {} + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataValidator" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Data Validator A", + "description": "Description", + "data_validator_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": {} + } + ], + "onchain_data_validators": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "data_validator_config_version": "0", + "status": "creation_pending" + } + ], + "ongoing_config_changes": [], + "archived_at": null + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Data validator implementation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + } + }, + "/data-validators/{data_validator_id}": { + "get": { + "tags": ["Data Validators"], + "summary": "Gets a data validator instance by ID.", + "parameters": [ + { + "in": "path", + "name": "data_validator_id", + "required": true, + "description": "Data validator instance ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataValidator" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Data Validator A", + "description": "Description", + "data_validator_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": {} + } + ], + "onchain_data_validators": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "data_validator_config_version": "0", + "status": "created" + } + ], + "ongoing_config_changes": [], + "archived_at": null + } + } + } + }, + "404": { + "description": "Data validator instance not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "put": { + "tags": ["Data Validators"], + "summary": "Updates a data validator instance.", + "parameters": [ + { + "in": "path", + "name": "data_validator_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateDataValidatorRequest" + }, + "example": { + "name": "Data Validator A Updated", + "description": "Updated description", + "onchain_data_validators": [ + { + "chain_selector": "16015286601757825753", + "initial_config": {} + }, + { + "chain_selector": "4949039107694359620", + "initial_config": {} + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataValidator" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Data Validator A Updated", + "description": "Updated description", + "data_validator_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753", "4949039107694359620"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": {} + } + ], + "onchain_data_validators": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "data_validator_config_version": "0", + "status": "created" + } + ], + "ongoing_config_changes": [], + "archived_at": null + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Data validator instance not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "patch": { + "tags": ["Data Validators"], + "summary": "Archives a data validator instance.", + "parameters": [ + { + "in": "path", + "name": "data_validator_id", + "required": true, + "description": "Data validator instance ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchDataValidatorRequest" + }, + "example": { + "status": "archived" + } + } + } + }, + "responses": { + "202": { + "description": "Request accepted. Returns the instance when archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataValidator" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Data Validator A", + "description": "Description", + "data_validator_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": {} + } + ], + "onchain_data_validators": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "data_validator_config_version": "0", + "status": "created" + } + ], + "ongoing_config_changes": [], + "archived_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. already archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Data validator instance not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + }, + "409": { + "description": "Cannot archive while on-chain deployment is being created or configuration change is in flight", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/data-validators/{data_validator_id}/configs": { + "patch": { + "tags": ["Data Validators"], + "summary": "Updates data validator configuration using JSON Patch.", + "description": "Applies [JSON Patch (RFC 6902)](https://jsonpatch.com/) operations to the instance configuration.\nUse `on_chains` to select deployments and supply `current_config_version` per chain (optimistic concurrency).\n", + "parameters": [ + { + "in": "path", + "name": "data_validator_id", + "description": "Data validator instance ID", + "schema": { + "$ref": "#/components/schemas/Id" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchDataValidatorConfigsRequest" + }, + "example": { + "patches": [ + { + "op": "add", + "path": "/someKey/-", + "value": "example" + } + ], + "on_chains": [ + { + "chain_selector": "16015286601757825753", + "current_config_version": "0" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataValidator" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Data Validator A", + "description": "Description", + "data_validator_implementation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_configs": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "config_json": {} + } + ], + "onchain_data_validators": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "data_validator_config_version": "1", + "status": "created" + } + ], + "ongoing_config_changes": [], + "archived_at": null + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Data validator instance not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + }, + "409": { + "description": "Configuration change rejected (version mismatch, instance not created, or ongoing change in flight)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/registries": { + "get": { + "tags": ["Registries"], + "summary": "Lists all registries.", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "$ref": "#/components/parameters/include_onchains" + }, + { + "in": "query", + "name": "type", + "required": false, + "description": "Type of registry. Can be \"pair\", \"identity\", or \"credential\". Defaults to \"pair\".", + "schema": { + "type": "string", + "enum": ["pair", "identity", "credential"], + "default": "pair" + } + }, + { + "in": "query", + "name": "include_granted", + "required": false, + "description": "When true, includes registries that other orgs have granted access to the caller, in addition to owned registries. Each registry in the response will have an access_type field (\"owned\" or \"granted\").", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListRegistriesResponse" + }, + "example": { + "total": 1, + "total_pages": 1, + "page": 1, + "registries": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Registry A", + "description": "Description for Registry A", + "org_id": "org-123", + "mode": "managed", + "chain_selectors": ["16015286601757825753"], + "identity_registries": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Identity Registry on Chain A", + "description": "Description for identity registry on Chain A", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "credential_registries": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Credential Registry on Chain A", + "description": "Description for credential registry on Chain A", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + } + } + }, + "post": { + "tags": ["Registries"], + "summary": "Creates a new registry with identity and credential registries.", + "description": "The address field is optional. If omitted, the registry will be deployed on-chain,\nits status will be \"creation_pending\" until created, and the mode will be `managed`. The possible modes are `managed` - when no address is provided - and `read_only` - when an address is passed - this mode does not allow any write changes to the registry like adding identities or credentials.\n", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateRegistryRequest" + }, + "example": { + "name": "Registry A", + "description": "Description for Registry A", + "identity_registries": [ + { + "name": "Identity Registry on Chain A", + "description": "Description for identity registry on Chain A", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ], + "credential_registries": [ + { + "name": "Credential Registry on Chain A", + "description": "Description for credential registry on Chain A", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Registry" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Registry A", + "description": "Description for Registry A", + "org_id": "org-123", + "mode": "managed", + "chain_selectors": ["16015286601757825753"], + "identity_registries": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Identity Registry on Chain A", + "description": "Description for identity registry on Chain A", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "credential_registries": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Credential Registry on Chain A", + "description": "Description for credential registry on Chain A", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "409": { + "description": "Registry already exists or conflict with existing registries", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/registries/{registry_id}": { + "get": { + "tags": ["Registries"], + "summary": "Gets a registry by ID.", + "parameters": [ + { + "in": "path", + "name": "registry_id", + "required": true, + "description": "Registry ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "in": "query", + "name": "type", + "required": false, + "description": "Type of registry. Can be \"pair\", \"identity\", or \"credential\". Defaults to \"pair\".", + "schema": { + "type": "string", + "enum": ["pair", "identity", "credential"], + "default": "pair" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Registry" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Registry A", + "description": "Description for Registry A", + "org_id": "org-123", + "mode": "managed", + "chain_selectors": ["16015286601757825753"], + "identity_registries": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Identity Registry on Chain A", + "description": "Description for identity registry on Chain A", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "credential_registries": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Credential Registry on Chain A", + "description": "Description for credential registry on Chain A", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "403": { + "description": "Access denied — caller does not own this registry and has no active access grant (including when the registry id is unknown)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Forbidden", + "message": "You do not have permission to access this resource" + } + } + } + } + } + }, + "put": { + "tags": ["Registries"], + "summary": "Updates a registry with identity and credential registries.", + "parameters": [ + { + "in": "path", + "name": "registry_id", + "required": true, + "description": "Registry ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateRegistryRequest" + }, + "example": { + "name": "Registry A", + "description": "Description for Registry A", + "identity_registries": [ + { + "name": "Identity Registry on Chain A", + "description": "Description for identity registry on Chain A", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + }, + { + "name": "Identity Registry on Chain B", + "description": "Description for identity registry on Chain B", + "chain_selector": "4949039107694359620", + "address": "0x1234567890123456789012345678901234567890" + } + ], + "credential_registries": [ + { + "name": "Credential Registry on Chain A", + "description": "Description for credential registry on Chain A", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + }, + { + "name": "Credential Registry on Chain B", + "description": "Description for credential registry on Chain B", + "chain_selector": "4949039107694359620", + "address": "0x1234567890123456789012345678901234567890" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Registry" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Registry A", + "description": "Description for Registry A", + "org_id": "org-123", + "mode": "managed", + "chain_selectors": ["16015286601757825753", "4949039107694359620"], + "identity_registries": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Identity Registry on Chain A", + "description": "Description for identity registry on Chain A", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + }, + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Identity Registry on Chain B", + "description": "Description for identity registry on Chain B", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "4949039107694359620", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "credential_registries": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Credential Registry on Chain A", + "description": "Description for credential registry on Chain A", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + }, + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Credential Registry on Chain B", + "description": "Description for credential registry on Chain B", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "4949039107694359620", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. registry already archived, cannot remove registry pairs - only adding new pairs is allowed)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Registry not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + }, + "409": { + "description": "Conflict with existing registries", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + }, + "patch": { + "tags": ["Registries"], + "summary": "Archives a registry. Rejected if identities exist.", + "parameters": [ + { + "in": "path", + "name": "registry_id", + "required": true, + "description": "Registry ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchRegistryRequest" + }, + "example": { + "status": "archived" + } + } + } + }, + "responses": { + "202": { + "description": "Request accepted. Returns registry when archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Registry" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Registry A", + "description": "Description for Registry A", + "org_id": "org-123", + "mode": "managed", + "chain_selectors": ["16015286601757825753"], + "identity_registries": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Identity Registry on Chain A", + "description": "Description for identity registry on Chain A", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "credential_registries": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Credential Registry on Chain A", + "description": "Description for credential registry on Chain A", + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": 1700000000, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. already archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Registry not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + }, + "409": { + "description": "Cannot archive - identities exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/registries/{registry_id}/access-grants": { + "get": { + "tags": ["Registries"], + "summary": "Lists all access grants for a registry. Requires registry ownership.", + "parameters": [ + { + "in": "path", + "name": "registry_id", + "required": true, + "description": "Registry ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListRegistryAccessGrantsResponse" + }, + "example": { + "total": 1, + "total_pages": 1, + "page": 1, + "access_grants": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "grantee_org_id": "org-456", + "grantor_org_id": "org-123", + "status": "active", + "granted_at": 1700000000, + "revoked_at": null + } + ] + } + } + } + }, + "403": { + "description": "Caller does not own the registry (including when the registry id is unknown)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Forbidden", + "message": "You do not have permission to access this resource" + } + } + } + } + } + }, + "post": { + "tags": ["Registries"], + "summary": "Grants registry access to another organization. Requires registry ownership.", + "parameters": [ + { + "in": "path", + "name": "registry_id", + "required": true, + "description": "Registry ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateRegistryAccessGrantRequest" + }, + "example": { + "grantee_org_id": "org-456" + } + } + } + }, + "responses": { + "201": { + "description": "Access grant created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegistryAccessGrant" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "grantee_org_id": "org-456", + "grantor_org_id": "org-123", + "status": "active", + "granted_at": 1700000000, + "revoked_at": null + } + } + } + }, + "400": { + "description": "Bad request (e.g. grantee_org_id missing or invalid)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "403": { + "description": "Caller does not own the registry (including when the registry id is unknown)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Forbidden", + "message": "You do not have permission to access this resource" + } + } + } + }, + "409": { + "description": "An active grant for this grantee already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/registries/{registry_id}/access-grants/{grantee_org_id}": { + "patch": { + "tags": ["Registries"], + "summary": "Revokes registry access for a grantee organization. Requires registry ownership.", + "parameters": [ + { + "in": "path", + "name": "registry_id", + "required": true, + "description": "Registry ID", + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "in": "path", + "name": "grantee_org_id", + "required": true, + "description": "Grantee organization ID", + "schema": { + "$ref": "#/components/schemas/OrgId" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchRegistryAccessGrantRequest" + }, + "example": { + "status": "revoked" + } + } + } + }, + "responses": { + "200": { + "description": "Access grant updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegistryAccessGrant" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "grantee_org_id": "org-456", + "grantor_org_id": "org-123", + "status": "revoked", + "granted_at": 1700000000, + "revoked_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. status value not accepted)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "403": { + "description": "Caller does not own the registry (including when the registry id is unknown)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Forbidden", + "message": "You do not have permission to access this resource" + } + } + } + }, + "404": { + "description": "No active access grant exists for the grantee (caller owns the registry)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + } + }, + "/identities": { + "get": { + "tags": ["Identities"], + "summary": "Lists all identities.", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "$ref": "#/components/parameters/include_onchains" + }, + { + "in": "query", + "name": "query", + "description": "Title of the identity, can be a substring", + "schema": { + "type": "string" + }, + "required": false + }, + { + "in": "query", + "name": "registry_id", + "description": "Filter by registry ID", + "required": false, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListIdentitiesResponse" + }, + "example": { + "total": 1, + "total_pages": 1, + "page": 1, + "identities": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Identity A", + "description": "Description for Identity A", + "entity_id": "1234567890", + "ccid": "0x1234567890123456789012345678901234567890", + "registryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_identities": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + }, + "403": { + "description": "Access denied — caller has no read access to registry_id (including when the registry id is unknown)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Forbidden", + "message": "You do not have permission to access this resource" + } + } + } + } + } + }, + "post": { + "tags": ["Identities"], + "summary": "Creates a new identity.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateIdentityRequest" + }, + "example": { + "title": "Identity A", + "description": "Description for Identity A", + "entity_id": "1234567890", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "onchain_identities": [ + { + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753" + } + ], + "credentials": [ + { + "credential_type_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "external_unique_id": "ext-123", + "expires_at": 1800000000 + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Identity" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Identity A", + "description": "Description for Identity A", + "entity_id": "1234567890", + "ccid": "0x1234567890123456789012345678901234567890", + "registryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_identities": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + } + } + } + }, + "/identities/batch": { + "post": { + "tags": ["Identities"], + "summary": "Creates multiple identities in a batch operation.", + "description": "Creates multiple identities at once. If any identity fails validation or creation, the entire batch fails.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateIdentitiesBatchRequest" + }, + "example": { + "identities": [ + { + "title": "Identity A", + "description": "Description for Identity A", + "entity_id": "1234567890", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "onchain_identities": [ + { + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753" + } + ] + }, + { + "title": "Identity B", + "description": "Description for Identity B", + "entity_id": "0987654321", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "onchain_identities": [ + { + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753" + } + ] + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateIdentitiesBatchResponse" + }, + "example": { + "results": [ + { + "status": "created", + "identity": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Identity A", + "description": "Description for Identity A", + "entity_id": "1234567890", + "ccid": "0x1234567890123456789012345678901234567890", + "registryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_identities": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + }, + { + "status": "failed", + "error": "Identity already exists" + } + ] + } + } + } + }, + "400": { + "description": "Bad request - validation failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "409": { + "description": "Conflict - one or more identities already exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/identities/{identity_id}": { + "get": { + "tags": ["Identities"], + "summary": "Gets an identity by identity ID.", + "parameters": [ + { + "in": "path", + "name": "identity_id", + "description": "Identity ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Identity" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Identity A", + "description": "Description for Identity A", + "entity_id": "1234567890", + "ccid": "0x1234567890123456789012345678901234567890", + "registryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_identities": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "403": { + "description": "Access denied — caller has no read access to the identity's registry (including when the identity id is unknown)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Forbidden", + "message": "You do not have permission to access this resource" + } + } + } + } + } + }, + "patch": { + "tags": ["Identities"], + "summary": "Update or archive an identity.", + "description": "Pass status=archived to archive, OR title/description/onchain_identities to update.\nMutually exclusive - if status is passed, update fields must not be passed (400).\nIf identity is already archived and status is passed, returns 400.\nIf identity is archived and update fields are passed, returns 400.\n", + "parameters": [ + { + "in": "path", + "name": "identity_id", + "description": "Identity ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchIdentityRequest" + }, + "example": { + "status": "archived" + } + } + } + }, + "responses": { + "202": { + "description": "Request accepted. Returns identity when update fields are used; identity when archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Identity" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Identity A", + "description": "Description for Identity A", + "entity_id": "1234567890", + "ccid": "0x1234567890123456789012345678901234567890", + "registryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_identities": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": 1700000000, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Identity not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "put": { + "tags": ["Identities"], + "summary": "Updates an identity.", + "description": "Returns 400 if identity is archived.", + "parameters": [ + { + "in": "path", + "name": "identity_id", + "description": "Identity ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateIdentityRequest" + }, + "example": { + "title": "Identity A Updated", + "description": "Updated description for Identity A", + "onchain_identities": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890" + }, + { + "chain_selector": "4949039107694359620", + "address": "0x1234567890123456789012345678901234567890" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Identity" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Identity A Updated", + "description": "Updated description for Identity A", + "entity_id": "1234567890", + "ccid": "0x1234567890123456789012345678901234567890", + "registryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753", "4949039107694359620"], + "onchain_identities": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request (e.g. identity is archived)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Identity not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + } + }, + "/credential-types": { + "get": { + "tags": ["Credential Types"], + "summary": "Lists all credential types.", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "in": "query", + "name": "registry_id", + "description": "Registry ID", + "required": false, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListCredentialTypesResponse" + }, + "example": { + "total": 1, + "total_pages": 1, + "page": 1, + "credential_types": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Credential Type A", + "description": "Description for Credential Type A", + "credential_type": "common.KYC", + "credential_type_hash": "0x1234567890123456789012345678901234567890", + "chain_selectors": ["16015286601757825753"], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + }, + "403": { + "description": "Access denied — caller has no read access to registry_id (including when the registry id is unknown)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Forbidden", + "message": "You do not have permission to access this resource" + } + } + } + } + } + }, + "post": { + "tags": ["Credential Types"], + "summary": "Register a credential type.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterCredentialTypeRequest" + }, + "example": { + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Credential Type A", + "description": "Description for Credential Type A", + "credential_type": "common.KYC" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CredentialType" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Credential Type A", + "description": "Description for Credential Type A", + "credential_type": "common.KYC", + "credential_type_hash": "0x1234567890123456789012345678901234567890", + "chain_selectors": ["16015286601757825753"], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "409": { + "description": "Credential type already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/credential-types/{credential_type_id}": { + "get": { + "tags": ["Credential Types"], + "summary": "Gets a credential type by its ID.", + "parameters": [ + { + "in": "path", + "name": "credential_type_id", + "description": "Credential Type ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CredentialType" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Credential Type A", + "description": "Description for Credential Type A", + "credential_type": "common.KYC", + "credential_type_hash": "0x1234567890123456789012345678901234567890", + "chain_selectors": ["16015286601757825753"], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "403": { + "description": "Access denied — caller has no read access to the credential type's registry (including when the credential type id is unknown)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Forbidden", + "message": "You do not have permission to access this resource" + } + } + } + } + } + }, + "put": { + "tags": ["Credential Types"], + "summary": "Update credential type details.", + "parameters": [ + { + "in": "path", + "name": "credential_type_id", + "description": "Credential Type ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCredentialTypeRequest" + }, + "example": { + "title": "Credential Type A Updated", + "description": "Updated description for Credential Type A" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CredentialType" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Credential Type A Updated", + "description": "Updated description for Credential Type A", + "credential_type": "common.KYC", + "credential_type_hash": "0x1234567890123456789012345678901234567890", + "chain_selectors": ["16015286601757825753"], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Credential type not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "patch": { + "tags": ["Credential Types"], + "summary": "Archive or update a credential type.", + "description": "Either status=archived to archive, OR title/description to update. Mutually exclusive.\nIf credentials exist with this credential type, archive returns 409. No onchain operations.\n", + "parameters": [ + { + "in": "path", + "name": "credential_type_id", + "description": "Credential Type ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchCredentialTypeRequest" + }, + "example": { + "status": "archived" + } + } + } + }, + "responses": { + "202": { + "description": "Request accepted. Returns credential type when archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CredentialType" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "registry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "title": "Credential Type A", + "description": "Description for Credential Type A", + "credential_type": "common.KYC", + "credential_type_hash": "0x1234567890123456789012345678901234567890", + "chain_selectors": ["16015286601757825753"], + "archived_at": 1700000000, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Credential type not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + }, + "409": { + "description": "Conflict - credentials exist with this credential type", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/credentials": { + "get": { + "tags": ["Credentials"], + "summary": "Lists all credentials.", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "$ref": "#/components/parameters/include_onchains" + }, + { + "in": "query", + "name": "credential_type_id", + "description": "Credential Type ID", + "required": false, + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "in": "query", + "name": "identity_id", + "description": "Identity ID", + "required": false, + "schema": { + "$ref": "#/components/schemas/Id" + } + }, + { + "in": "query", + "name": "entity_id", + "description": "Entity ID", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "registry_id", + "description": "Registry ID", + "required": false, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListCredentialsResponse" + }, + "example": { + "total": 1, + "total_pages": 1, + "page": 1, + "credentials": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "credential_type_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "external_unique_id": "ext-credential-123", + "expires_at": 1800000000, + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_credentials": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "status": "created", + "expires_at": 1800000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + }, + "403": { + "description": "Access denied — caller has no read access to registry_id (including when the registry id is unknown)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Forbidden", + "message": "You do not have permission to access this resource" + } + } + } + } + } + }, + "post": { + "tags": ["Credentials"], + "summary": "Register a credential.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterCredentialRequest" + }, + "example": { + "credential_type_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "external_unique_id": "ext-credential-123", + "expires_at": 1800000000 + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Credential" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "credential_type_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "external_unique_id": "ext-credential-123", + "expires_at": 1800000000, + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_credentials": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "status": "created", + "expires_at": 1800000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "409": { + "description": "Credential already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/credentials/{credential_id}": { + "get": { + "tags": ["Credentials"], + "summary": "Gets a credential by its ID.", + "parameters": [ + { + "in": "path", + "name": "credential_id", + "description": "Credential ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Credential" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "credential_type_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "external_unique_id": "ext-credential-123", + "expires_at": 1800000000, + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_credentials": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "status": "created", + "expires_at": 1800000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "403": { + "description": "Access denied — caller has no read access to the credential's registry (including when the credential id is unknown)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Forbidden", + "message": "You do not have permission to access this resource" + } + } + } + } + } + }, + "put": { + "tags": ["Credentials"], + "summary": "Update credential details.", + "parameters": [ + { + "in": "path", + "name": "credential_id", + "description": "Credential ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCredentialRequest" + }, + "example": { + "external_unique_id": "ext-credential-456", + "expires_at": 1900000000 + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Credential" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "credential_type_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "external_unique_id": "ext-credential-456", + "expires_at": 1900000000, + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_credentials": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "status": "created", + "expires_at": 1900000000 + } + ], + "archived_at": null, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Credential not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + }, + "patch": { + "tags": ["Credentials"], + "summary": "Update or archive a credential.", + "description": "Pass status=archived to archive, OR external_unique_id/expires_at to update.\nMutually exclusive - if status is passed, update fields must not be passed (400).\nIf credential is already archived and status is passed, returns 400.\n", + "parameters": [ + { + "in": "path", + "name": "credential_id", + "description": "Credential ID", + "required": true, + "schema": { + "$ref": "#/components/schemas/Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchCredentialRequest" + }, + "example": { + "status": "archived" + } + } + } + }, + "responses": { + "202": { + "description": "Request accepted. Returns credential when update fields are used; empty body when archived.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Credential" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "credential_type_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "external_unique_id": "ext-credential-123", + "expires_at": 1800000000, + "identity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selectors": ["16015286601757825753"], + "onchain_credentials": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "chain_selector": "16015286601757825753", + "status": "created", + "expires_at": 1800000000 + } + ], + "archived_at": 1700000000, + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "404": { + "description": "Credential not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + } + }, + "/wallets": { + "get": { + "tags": ["Wallets"], + "summary": "Lists all wallets.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListWalletsResponse" + }, + "example": { + "wallets": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "chain_selector": "16015286601757825753", + "owner_address": "0x1234567890123456789012345678901234567890", + "wallet_address": "0x1234567890123456789012345678901234567890", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + } + } + }, + "post": { + "tags": ["Wallets"], + "summary": "Creates wallets on networks.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateWalletsRequest" + }, + "example": { + "wallets": [ + { + "owner_address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "address": ["0x1234567890123456789012345678901234567890"] + }, + { + "owner_address": "0x1234567890123456789012345678901234567890", + "chain_selector": "4949039107694359620", + "address": ["0x1234567890123456789012345678901234567890"] + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListWalletsResponse" + }, + "example": { + "wallets": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "chain_selector": "16015286601757825753", + "owner_address": "0x1234567890123456789012345678901234567890", + "wallet_address": "0x1234567890123456789012345678901234567890", + "status": "created", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Bad request", + "message": "Invalid request parameters" + } + } + } + }, + "409": { + "description": "Wallet already exists for this chain selector", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Already exists", + "message": "Resource already exists" + } + } + } + } + } + } + }, + "/networks": { + "get": { + "tags": ["Networks"], + "summary": "Lists all networks.", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/page_size" + }, + { + "in": "query", + "name": "chain_id", + "description": "Chain ID", + "schema": { + "type": "string" + }, + "required": false + }, + { + "in": "query", + "name": "chain_family", + "description": "Chain family", + "schema": { + "type": "string" + }, + "required": false + }, + { + "in": "query", + "name": "show_unavailable", + "description": "When true, lists mainnet networks too (even if the org lacks mainnet_allowed and cannot use them for on-chain operations).", + "schema": { + "type": "boolean", + "default": false + }, + "required": false + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListNetworksResponse" + }, + "example": { + "total": 3, + "total_pages": 1, + "page": 1, + "networks": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Ethereum Sepolia", + "chain_id": "11155111", + "chain_selector": "16015286601757825753", + "chain_family": "evm", + "created_at": 1700000000, + "updated_at": 1700000000 + } + ] + } + } + } + } + } + } + }, + "/networks/{chain_selector}": { + "get": { + "tags": ["Networks"], + "summary": "Gets a network by chain selector.", + "parameters": [ + { + "in": "path", + "name": "chain_selector", + "description": "Chain selector", + "required": true, + "schema": { + "$ref": "#/components/schemas/ChainSelector" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Network" + }, + "example": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Ethereum Sepolia", + "chain_id": "11155111", + "chain_selector": "16015286601757825753", + "chain_family": "evm", + "created_at": 1700000000, + "updated_at": 1700000000 + } + } + } + }, + "404": { + "description": "Network not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + }, + "example": { + "error": "Not found", + "message": "Resource not found" + } + } + } + } + } + } + } + } +} diff --git a/public/api/ace/evaluation/openapi.json b/public/api/ace/evaluation/openapi.json new file mode 100644 index 00000000000..f5fc0f5d112 --- /dev/null +++ b/public/api/ace/evaluation/openapi.json @@ -0,0 +1,329 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "Chainlink ACE Evaluation API", + "version": "0.1.0", + "description": "MVP API for starting and monitoring managed offchain policy evaluations. Its interfaces and capabilities can change during Beta. Contact your Chainlink representative for help with setup. Use the same API key authentication as the ACE Coordinator API." + }, + "servers": [ + { + "url": "https://ace.api.chain.link/v1/evaluation" + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + { + "name": "Evaluation", + "description": "Request and monitor managed offchain policy permits." + }, + { + "name": "Health Check" + } + ], + "paths": { + "/evaluate": { + "post": { + "tags": ["Evaluation"], + "summary": "Start permit evaluation", + "description": "Derives a deterministic permit_id from the authenticated organization and unique_evaluation_id, starts the evaluation, and returns the same evaluation on idempotent retries.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvaluateRequest" + }, + "example": { + "caller_address": "0x1111111111111111111111111111111111111111", + "subject": "0x2222222222222222222222222222222222222222", + "function_signature": "transfer(address,uint256)", + "parameters": { + "to": "0x3333333333333333333333333333333333333333", + "amount": "100" + }, + "permit_parameters": [ + "0x0000000000000000000000001111111111111111111111111111111111111111", + "0x0000000000000000000000003333333333333333333333333333333333333333", + "0x0000000000000000000000000000000000000000000000000000000000000064" + ], + "chain_selector": "", + "unique_evaluation_id": "transfer-018f6b3e-7c42-7a1f-a8ed-5ecf90c03b30" + } + } + } + }, + "responses": { + "200": { + "description": "Evaluation accepted or duplicate request returning existing state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvaluateResponse" + } + } + } + }, + "400": { + "description": "Invalid input", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "500": { + "description": "Internal error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + } + } + } + }, + "/evaluate/{permitId}": { + "get": { + "tags": ["Evaluation"], + "operationId": "getEvaluationByPermitId", + "summary": "Get evaluation run by permit ID", + "description": "Returns the evaluation for the authenticated organization. Returns 404 when the permit does not exist or belongs to another organization.", + "parameters": [ + { + "name": "permitId", + "in": "path", + "required": true, + "description": "Permit identifier returned by POST /evaluate.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Evaluation found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationDetailResponse" + } + } + } + }, + "400": { + "description": "Invalid permit ID", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "404": { + "description": "Permit missing or not visible to this organization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "500": { + "description": "Internal error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + } + } + } + }, + "/health-check": { + "get": { + "tags": ["Health Check"], + "summary": "Health check", + "security": [], + "responses": { + "200": { + "description": "Service is healthy", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthCheck" + }, + "example": { + "status": "ok" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "Authorization", + "description": "API key authentication. Format: Apikey " + } + }, + "schemas": { + "EvaluateRequest": { + "type": "object", + "required": [ + "caller_address", + "subject", + "function_signature", + "parameters", + "chain_selector", + "unique_evaluation_id" + ], + "properties": { + "caller_address": { + "$ref": "#/components/schemas/EthAddress" + }, + "subject": { + "$ref": "#/components/schemas/EthAddress" + }, + "function_signature": { + "type": "string", + "description": "Canonical ABI function signature, for example transfer(address,uint256). The coordinator derives the four-byte selector from this value." + }, + "parameters": { + "type": "object", + "additionalProperties": true, + "description": "Structured function parameters stored with the evaluation as contextual data." + }, + "permit_parameters": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Optional ABI-encoded permit parameters as 0x-prefixed 32-byte words. ACE forwards them to the workflow for address screening and exact onchain permit binding. For transfer(address,uint256), supply [from, to, amount]." + }, + "chain_selector": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "description": "Chain selector for the target and CADV deployment." + }, + "unique_evaluation_id": { + "type": "string", + "description": "Client-generated identifier unique to this evaluation intent. Reusing it within the same organization returns the existing evaluation." + } + } + }, + "EvaluateResponse": { + "type": "object", + "required": ["permit_id", "status"], + "properties": { + "permit_id": { + "type": "string", + "description": "Hex-encoded bytes32 identifier for the evaluation." + }, + "status": { + "$ref": "#/components/schemas/EvaluationRunStatus" + } + } + }, + "EvaluationDetailResponse": { + "type": "object", + "required": ["permit_id", "status"], + "properties": { + "permit_id": { + "type": "string", + "description": "Permit identifier returned by POST /evaluate." + }, + "status": { + "$ref": "#/components/schemas/EvaluationRunStatus" + }, + "reason": { + "type": "string", + "nullable": true, + "description": "Details for a rejected or failed evaluation when available." + }, + "workflow_execution_id": { + "type": "string", + "nullable": true, + "description": "CRE workflow execution identifier when available." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Permit expiry when available. Managed wallet risk permits currently do not expire, so this is normally null." + } + } + }, + "EvaluationRunStatus": { + "type": "string", + "description": "Lifecycle status of an offchain policy evaluation.", + "enum": ["evaluating", "rejected", "approving", "error", "ready"] + }, + "EthAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Ethereum address" + }, + "ApiErrorResponse": { + "type": "object", + "required": ["error", "message"], + "properties": { + "message": { + "type": "string" + }, + "error": { + "type": "string", + "enum": ["Already exists", "Internal error", "Not found", "Bad request", "Unauthorized", "Forbidden"] + } + } + }, + "HealthCheck": { + "type": "object", + "required": ["status"], + "properties": { + "status": { + "type": "string", + "example": "ok" + } + } + } + } + } +} diff --git a/public/api/ace/reporting/openapi.json b/public/api/ace/reporting/openapi.json new file mode 100644 index 00000000000..9283517579d --- /dev/null +++ b/public/api/ace/reporting/openapi.json @@ -0,0 +1,2918 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Chainlink ACE Reporting API", + "version": "1.0.0", + "description": "Read-only API for querying onchain state, transaction history, policy configurations, identities, and credentials" + }, + "servers": [ + { + "url": "https://ace.api.chain.link/v1/reporting", + "description": "Production" + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-constants": { + "address": "0x1234567890123456789012345678901234567890", + "eth_sepolia_chain_selector": "16015286601757825753", + "timestamp": "2024-05-17T00:00:00Z", + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "bytes32": "0xaabbccdd00112233aabbccdd00112233aabbccdd00112233aabbccdd00112233", + "method_selector": "0xa9059cbb", + "hex_bytes": "0x7b226e616d65223a22416c696365227d", + "error_bad_request": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + }, + "error_not_found": { + "message": "Resource not found", + "code": "NOT_FOUND" + }, + "error_internal": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + }, + "paths": { + "/health": { + "get": { + "summary": "Health check endpoint", + "operationId": "getHealth", + "tags": ["Health Check"], + "security": [], + "responses": { + "200": { + "description": "Service is healthy", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthResponse" + }, + "example": { + "status": "ok" + } + } + } + } + } + } + }, + "/policies": { + "get": { + "summary": "List policies", + "description": "Retrieve a list of policies. Returns state as of the specified time.", + "operationId": "listPolicies", + "tags": ["Policies"], + "parameters": [ + { + "name": "as_of", + "in": "query", + "description": "Show policies active as of given time (ISO 8601 format). Returns historical state.", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-05-17T00:00:00Z" + } + }, + { + "name": "policy_engine_address", + "in": "query", + "description": "Filter by policy engine address", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "owner", + "in": "query", + "description": "Filter by owner address", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "chain_selector", + "in": "query", + "description": "Filter by chain selector (uint64)", + "required": false, + "schema": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + { + "name": "include_policy_details", + "in": "query", + "description": "Include policy state JSON in the response", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "page_size", + "in": "query", + "description": "Number of results per page", + "required": false, + "schema": { + "$ref": "#/components/schemas/PageSize" + } + }, + { + "$ref": "#/components/parameters/AdminOrgId" + }, + { + "name": "page_token", + "in": "query", + "description": "Token for pagination", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of policies", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PolicyListResponse" + }, + "example": { + "policies": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "owner": "0x1234567890123456789012345678901234567890", + "name": "Policy A", + "description": "Description for Policy A", + "policy_engine_address": "0x1234567890123456789012345678901234567890", + "version": 1, + "state": null, + "effective_from": "2024-05-17T00:00:00Z", + "effective_to": null, + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + ], + "next_page_token": null + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + } + } + } + } + } + }, + "/policies/{chain_selector}/{address}": { + "get": { + "summary": "Get policy by chain and address", + "description": "Retrieve a specific policy by its chain selector and address at a given point in time. Returns the policy version that was active at the specified as_of time.", + "operationId": "getPolicy", + "tags": ["Policies"], + "parameters": [ + { + "name": "chain_selector", + "in": "path", + "required": true, + "description": "Chain selector where the policy is deployed", + "schema": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + { + "name": "address", + "in": "path", + "required": true, + "description": "Policy contract address", + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "as_of", + "in": "query", + "description": "Show policy state as of given time (ISO 8601 format). Returns historical state.", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-05-17T00:00:00Z" + } + }, + { + "name": "include_policy_details", + "in": "query", + "description": "Include policy state JSON in the response", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Policy details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PolicyData" + }, + "example": { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "owner": "0x1234567890123456789012345678901234567890", + "name": "Policy A", + "description": "Description for Policy A", + "policy_engine_address": "0x1234567890123456789012345678901234567890", + "version": 1, + "state": null, + "effective_from": "2024-05-17T00:00:00Z", + "effective_to": null, + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + } + } + } + }, + "404": { + "description": "Policy not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Resource not found", + "code": "NOT_FOUND" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + } + } + } + } + } + }, + "/policies/{chain_selector}/{address}/versions/{version}": { + "get": { + "summary": "Get specific policy version", + "description": "Retrieve a specific version of a policy (version can be numeric or \"latest\")", + "operationId": "getPolicyVersion", + "tags": ["Policies"], + "parameters": [ + { + "name": "chain_selector", + "in": "path", + "required": true, + "description": "Chain selector where the policy is deployed", + "schema": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + { + "name": "address", + "in": "path", + "required": true, + "description": "Policy contract address", + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "description": "Version number or \"latest\"", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Policy version", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PolicyData" + }, + "example": { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "owner": "0x1234567890123456789012345678901234567890", + "name": "Policy A", + "description": "Description for Policy A", + "policy_engine_address": "0x1234567890123456789012345678901234567890", + "version": 1, + "state": null, + "effective_from": "2024-05-17T00:00:00Z", + "effective_to": null, + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + } + } + } + }, + "404": { + "description": "Policy or version not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Resource not found", + "code": "NOT_FOUND" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + } + } + } + } + } + }, + "/identities": { + "get": { + "summary": "List identities", + "description": "Retrieve a list of identities with various filters.\n- Filter by wallet address to find which identity owns it\n- Filter by identity registry to see all identities in a registry\n- Filter by credential type to find identities with specific credentials\n- Filter by credential registry to see identities with credentials from that registry\n", + "operationId": "listIdentities", + "tags": ["Identities"], + "parameters": [ + { + "name": "as_of", + "in": "query", + "description": "Show identities active as of given time (ISO 8601 format). Returns historical state.", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-05-17T00:00:00Z" + } + }, + { + "name": "address", + "in": "query", + "description": "Filter by wallet address (find identity that owns this address)", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "identity_registry", + "in": "query", + "description": "Filter by identity registry address", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "credential_registry", + "in": "query", + "description": "Filter by credential registry address (find identities with credentials from this registry)", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "credential_type_id", + "in": "query", + "description": "Filter by credential type ID (find identities with this credential type)", + "required": false, + "schema": { + "$ref": "#/components/schemas/Bytes32" + } + }, + { + "name": "chain_selector", + "in": "query", + "description": "Filter by chain selector", + "required": false, + "schema": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + { + "name": "include_credential_details", + "in": "query", + "description": "Include full credential data for each identity", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "page_size", + "in": "query", + "description": "Number of results per page", + "required": false, + "schema": { + "$ref": "#/components/schemas/PageSize" + } + }, + { + "$ref": "#/components/parameters/AdminOrgId" + }, + { + "name": "page_token", + "in": "query", + "description": "Token for pagination", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of identities", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IdentityListResponse" + }, + "example": { + "identities": [ + { + "ccid": "0xaabbccdd00112233aabbccdd00112233aabbccdd00112233aabbccdd00112233", + "registries": [ + { + "registry_address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "addresses": [ + { + "address": "0x1234567890123456789012345678901234567890", + "effective_from": "2024-05-17T00:00:00Z", + "effective_to": null, + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + ], + "block_timestamp": "2024-05-17T00:00:00Z", + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + ], + "credentials": [ + { + "credential_type_id": "0xaabbccdd00112233aabbccdd00112233aabbccdd00112233aabbccdd00112233", + "credential_registry": { + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "block_timestamp": "2024-05-17T00:00:00Z", + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + }, + "expires_at": null, + "credential_data": "0x7b226e616d65223a22416c696365227d", + "effective_from": "2024-05-17T00:00:00Z", + "effective_to": null, + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + ] + } + ], + "next_page_token": null + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + } + } + } + } + } + }, + "/identities/{ccid}": { + "get": { + "summary": "Get identity by CCID", + "description": "Retrieve a specific identity by its Cross-Chain ID (CCID).\nReturns the full identity with all registries, addresses, and optionally credentials.\n", + "operationId": "getIdentityByCCID", + "tags": ["Identities"], + "parameters": [ + { + "name": "ccid", + "in": "path", + "required": true, + "description": "Cross-Chain ID (CCID) - 66 character hex string", + "schema": { + "$ref": "#/components/schemas/Bytes32" + } + }, + { + "name": "as_of", + "in": "query", + "description": "Show identity state as of given time (ISO 8601 format). Returns historical state.", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-05-17T00:00:00Z" + } + }, + { + "name": "include_credential_details", + "in": "query", + "description": "Include full credential data", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Identity details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IdentityData" + }, + "example": { + "ccid": "0xaabbccdd00112233aabbccdd00112233aabbccdd00112233aabbccdd00112233", + "registries": [ + { + "registry_address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "addresses": [ + { + "address": "0x1234567890123456789012345678901234567890", + "effective_from": "2024-05-17T00:00:00Z", + "effective_to": null, + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + ], + "block_timestamp": "2024-05-17T00:00:00Z", + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + ], + "credentials": [ + { + "credential_type_id": "0xaabbccdd00112233aabbccdd00112233aabbccdd00112233aabbccdd00112233", + "credential_registry": { + "address": "0x1234567890123456789012345678901234567890", + "chain_selector": "16015286601757825753", + "block_timestamp": "2024-05-17T00:00:00Z", + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + }, + "expires_at": null, + "credential_data": "0x7b226e616d65223a22416c696365227d", + "effective_from": "2024-05-17T00:00:00Z", + "effective_to": null, + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + ] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + } + } + } + }, + "404": { + "description": "Identity not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Resource not found", + "code": "NOT_FOUND" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + } + } + } + } + } + }, + "/targets": { + "get": { + "summary": "List targets", + "description": "Retrieve a list of targets (contracts protected by policy engines).\n- Filter by policy engine address to see targets attached to a specific engine\n- Filter by chain selector to see targets on a specific chain\n- Use include_policy_details to get full policy configuration for each target\n", + "operationId": "listTargets", + "tags": ["Targets"], + "parameters": [ + { + "name": "as_of", + "in": "query", + "description": "Show targets active as of given time (ISO 8601 format). Returns historical state.", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-05-17T00:00:00Z" + } + }, + { + "name": "policy_engine_address", + "in": "query", + "description": "Filter by policy engine address", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "chain_selector", + "in": "query", + "description": "Filter by chain selector", + "required": false, + "schema": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + { + "name": "include_policy_details", + "in": "query", + "description": "Include policy state JSON in the response", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "page_size", + "in": "query", + "description": "Number of results per page", + "required": false, + "schema": { + "$ref": "#/components/schemas/PageSize" + } + }, + { + "$ref": "#/components/parameters/AdminOrgId" + }, + { + "name": "page_token", + "in": "query", + "description": "Token for pagination", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of targets", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TargetListResponse" + }, + "example": { + "targets": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "policy_engines": [ + { + "policy_engine_address": "0x1234567890123456789012345678901234567890", + "attached_at": "2024-05-17T00:00:00Z", + "detached_at": null, + "engine_default_behavior": "ALLOW", + "target_default_behavior": "ALLOW", + "protected_methods": [ + { + "method_selector": "0xa9059cbb", + "extractor": { + "address": "0x1234567890123456789012345678901234567890", + "effective_from": "2024-05-17T00:00:00Z", + "effective_to": null, + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + }, + "policies": [ + { + "policy_address": "0x1234567890123456789012345678901234567890", + "policy_name": "Policy A", + "parameter_names": ["amount", "recipient"], + "mapper": null, + "version": 1, + "state": null, + "effective_from": "2024-05-17T00:00:00Z", + "effective_to": null, + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + ] + } + ], + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + ] + } + ], + "next_page_token": null + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + } + } + } + } + } + }, + "/targets/{chain_selector}/{address}": { + "get": { + "summary": "Get target by chain and address", + "description": "Retrieve a specific target by its chain selector and address.\nReturns the target with all policy engine attachments and their configurations.\n", + "operationId": "getTarget", + "tags": ["Targets"], + "parameters": [ + { + "name": "chain_selector", + "in": "path", + "required": true, + "description": "Chain selector where the target is deployed", + "schema": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + { + "name": "address", + "in": "path", + "required": true, + "description": "Target contract address", + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "as_of", + "in": "query", + "description": "Show target state as of given time (ISO 8601 format). Returns historical state.", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-05-17T00:00:00Z" + } + }, + { + "name": "include_policy_details", + "in": "query", + "description": "Include policy state JSON in the response", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Target details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TargetData" + }, + "example": { + "chain_selector": "16015286601757825753", + "address": "0x1234567890123456789012345678901234567890", + "policy_engines": [ + { + "policy_engine_address": "0x1234567890123456789012345678901234567890", + "attached_at": "2024-05-17T00:00:00Z", + "detached_at": null, + "engine_default_behavior": "ALLOW", + "target_default_behavior": "ALLOW", + "protected_methods": [ + { + "method_selector": "0xa9059cbb", + "extractor": { + "address": "0x1234567890123456789012345678901234567890", + "effective_from": "2024-05-17T00:00:00Z", + "effective_to": null, + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + }, + "policies": [ + { + "policy_address": "0x1234567890123456789012345678901234567890", + "policy_name": "Policy A", + "parameter_names": ["amount", "recipient"], + "mapper": null, + "version": 1, + "state": null, + "effective_from": "2024-05-17T00:00:00Z", + "effective_to": null, + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + ] + } + ], + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + } + ] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + } + } + } + }, + "404": { + "description": "Target not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Resource not found", + "code": "NOT_FOUND" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + } + } + } + } + } + }, + "/transactions": { + "get": { + "summary": "List transactions", + "description": "Retrieve a paginated list of transactions that triggered at least one\nACE policy engine evaluation (PolicyRunComplete event). Only transactions\nbelonging to the caller's organization are returned. Setup-only\ntransactions (e.g. IdentityRegistered) that did not produce a policy\nrun are excluded.\n\nSort contract:\n- `sort` must include both `chain_selector` and `block_number` (comma-separated)\n- Only `block_number` supports a direction prefix (`-` for descending, `+` or no prefix for ascending)\n- Valid values: `chain_selector,block_number`, `chain_selector,-block_number`\n- Default: `chain_selector,-block_number` (newest first within each chain)\n", + "operationId": "listTransactions", + "tags": ["Transactions"], + "parameters": [ + { + "name": "chain_selector", + "in": "query", + "description": "Filter by chain selector", + "required": false, + "schema": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + { + "name": "from", + "in": "query", + "description": "Inclusive lower bound on block_timestamp (ISO 8601 format)", + "required": false, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-01-01T00:00:00Z" + } + }, + { + "name": "to", + "in": "query", + "description": "Inclusive upper bound on block_timestamp (ISO 8601 format)", + "required": false, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-01-31T23:59:59Z" + } + }, + { + "name": "from_address", + "in": "query", + "description": "Filter by sender address", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "to_address", + "in": "query", + "description": "Filter by recipient address", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "target_contract_address", + "in": "query", + "description": "Filter by target contract address (contract must be integrated with ACE)", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "function_selector", + "in": "query", + "description": "Filter by function selector (4-byte hex, e.g. 0xa9059cbb)", + "required": false, + "schema": { + "$ref": "#/components/schemas/MethodSelector" + } + }, + { + "name": "policy_address", + "in": "query", + "description": "Filter by policy contract address that evaluated the transaction", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "policy_engine_address", + "in": "query", + "description": "Filter by policy engine address that evaluated the transaction", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "include_policy_details", + "in": "query", + "description": "Include policy state JSON in the response", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "sort", + "in": "query", + "description": "Sort order for results. Must include both chain_selector and block_number (comma-separated).\nOnly block_number supports a direction prefix (- for descending, + or no prefix for ascending).\nValid values: chain_selector,block_number or chain_selector,-block_number.\nDefault: chain_selector,-block_number\n", + "required": false, + "schema": { + "type": "string", + "default": "chain_selector,-block_number", + "example": "chain_selector,-block_number" + } + }, + { + "name": "page_size", + "in": "query", + "description": "Number of results per page", + "required": false, + "schema": { + "$ref": "#/components/schemas/PageSize" + } + }, + { + "$ref": "#/components/parameters/AdminOrgId" + }, + { + "name": "page_token", + "in": "query", + "description": "Token for pagination", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of transactions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionListResponse" + }, + "example": { + "transactions": [ + { + "chain_selector": "16015286601757825753", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "block_timestamp": "2024-05-17T00:00:00Z", + "from_address": "0x1234567890123456789012345678901234567890", + "to_address": "0x1234567890123456789012345678901234567890", + "value": "0", + "data": "0x7b226e616d65223a22416c696365227d", + "gas_used": 85000, + "gas_limit": 100000, + "gas_price": "20000000000", + "gas_fee_cap": "25000000000", + "gas_tip_cap": "1000000000", + "effective_gas_price": "20000000000", + "cumulative_gas_used": 500000, + "tx_type": 2, + "revert_reason": null, + "l1_gas_used": null, + "l1_fee": null, + "policy_runs": [ + { + "on_chain": { + "target_contract_address": "0x1234567890123456789012345678901234567890", + "engine_address": "0x1234567890123456789012345678901234567890", + "method": { + "selector": "0xa9059cbb" + }, + "policies": [ + { + "address": "0x1234567890123456789012345678901234567890", + "version": 1, + "name": "Policy A", + "mapper_address": null, + "policy_state": null + } + ], + "params": { + "amount": 1000, + "recipient": "0x1234567890123456789012345678901234567890" + }, + "context": null, + "engine_default_behavior": "ALLOW", + "target_default_behavior": "ALLOW", + "extractor_address": "0x1234567890123456789012345678901234567890", + "log_index": 0 + } + } + ] + } + ], + "next_page_token": null + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + } + } + } + } + } + }, + "/permits": { + "get": { + "summary": "List stored permits", + "description": "Paginated list of stored on-chain permits. Only permits for policies\nbelonging to the caller's organization are returned.\n", + "operationId": "listPermits", + "tags": ["Permits"], + "parameters": [ + { + "name": "chain_selector", + "in": "query", + "description": "Filter by chain selector", + "required": false, + "schema": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + { + "name": "policy_address", + "in": "query", + "description": "Filter by policy contract address that stored the permit", + "required": false, + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "permit_id", + "in": "query", + "description": "Filter by permit ID (lowercase 0x-prefixed bytes32)", + "required": false, + "schema": { + "$ref": "#/components/schemas/Bytes32" + } + }, + { + "name": "from", + "in": "query", + "description": "Inclusive lower bound on block_timestamp (ISO 8601)", + "required": false, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-01-01T00:00:00Z" + } + }, + { + "name": "to", + "in": "query", + "description": "Inclusive upper bound on block_timestamp (ISO 8601)", + "required": false, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-01-31T23:59:59Z" + } + }, + { + "name": "sort", + "in": "query", + "description": "Sort order. Must include both chain_selector and block_number (comma-separated).\nOnly block_number supports a direction prefix (- for descending, + or no prefix for ascending).\nValid values: chain_selector,block_number or chain_selector,-block_number.\nDefault: chain_selector,-block_number\n", + "required": false, + "schema": { + "type": "string", + "default": "chain_selector,-block_number", + "example": "chain_selector,-block_number" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/PageSize" + } + }, + { + "$ref": "#/components/parameters/AdminOrgId" + }, + { + "name": "page_token", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Stored permits (may be empty)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermitListResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + } + } + } + }, + "404": { + "description": "Policy not found for the caller on this chain, when policy_address is set", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Resource not found", + "code": "NOT_FOUND" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + } + } + } + } + } + }, + "/registry-usage/events": { + "get": { + "summary": "List IDV registry usage events", + "description": "Paginated list of proven registry usage. The caller must own the requested registry for the given chain.\n\nSort contract (same as /transactions):\n- `sort` must include both `chain_selector` and `block_number` (comma-separated)\n- Only `block_number` supports a direction prefix (`-` for descending, `+` or no prefix for ascending)\n- Valid values: `chain_selector,block_number`, `chain_selector,-block_number`\n- Default: `chain_selector,-block_number`\n", + "operationId": "listRegistryUsageEvents", + "tags": ["Registry usage"], + "parameters": [ + { + "name": "registry_type", + "in": "query", + "required": true, + "description": "Whether the caller's registry filter is a credential or identity registry address", + "schema": { + "$ref": "#/components/schemas/RegistryType" + } + }, + { + "name": "chain_selector", + "in": "query", + "required": true, + "description": "Chain selector where the registry and runs are evaluated", + "schema": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + { + "name": "registry_address", + "in": "query", + "required": true, + "description": "Registry contract address owned by the caller for this chain", + "schema": { + "$ref": "#/components/schemas/EvmAddress" + } + }, + { + "name": "ccid", + "in": "query", + "required": false, + "description": "Optional filter — only return rows for this CCID (lowercase 0x-prefixed bytes32)", + "schema": { + "$ref": "#/components/schemas/Bytes32" + } + }, + { + "name": "from", + "in": "query", + "description": "Inclusive lower bound on policy run block_timestamp (ISO 8601)", + "required": false, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-01-01T00:00:00Z" + } + }, + { + "name": "to", + "in": "query", + "description": "Inclusive upper bound on policy run block_timestamp (ISO 8601)", + "required": false, + "schema": { + "type": "string", + "format": "date-time", + "example": "2024-01-31T23:59:59Z" + } + }, + { + "name": "sort", + "in": "query", + "description": "Sort order. Must include both chain_selector and block_number (comma-separated).\nOnly block_number supports a direction prefix (- for descending, + or no prefix for ascending).\nValid values: chain_selector,block_number or chain_selector,-block_number.\nDefault: chain_selector,-block_number\n", + "required": false, + "schema": { + "type": "string", + "default": "chain_selector,-block_number", + "example": "chain_selector,-block_number" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/PageSize" + } + }, + { + "$ref": "#/components/parameters/AdminOrgId" + }, + { + "name": "page_token", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Registry usage events (may be empty)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegistryUsageEventListResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + } + } + } + }, + "404": { + "description": "Registry not found for the caller on this chain, or not owned by the caller", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Resource not found", + "code": "NOT_FOUND" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + } + } + } + } + } + }, + "/transactions/{chain_selector}/{tx_hash}": { + "get": { + "summary": "Get transaction by chain selector and hash", + "description": "Retrieve a specific transaction by its chain selector and transaction hash.\nThe transaction must have triggered at least one ACE policy engine\nevaluation (PolicyRunComplete event) within the caller's organization;\notherwise a 404 is returned. Setup-only transactions\n(e.g. IdentityRegistered) are not accessible through this endpoint.\n", + "operationId": "getTransaction", + "tags": ["Transactions"], + "parameters": [ + { + "name": "chain_selector", + "in": "path", + "required": true, + "description": "Chain selector where the transaction was executed", + "schema": { + "$ref": "#/components/schemas/ChainSelector" + } + }, + { + "name": "tx_hash", + "in": "path", + "required": true, + "description": "Transaction hash", + "schema": { + "$ref": "#/components/schemas/TransactionHash" + } + }, + { + "name": "include_policy_details", + "in": "query", + "description": "Include policy state JSON in the response", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Transaction details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionData" + }, + "example": { + "chain_selector": "16015286601757825753", + "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "block_number": 19876543, + "block_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "block_timestamp": "2024-05-17T00:00:00Z", + "from_address": "0x1234567890123456789012345678901234567890", + "to_address": "0x1234567890123456789012345678901234567890", + "value": "0", + "data": "0x7b226e616d65223a22416c696365227d", + "gas_used": 85000, + "gas_limit": 100000, + "gas_price": "20000000000", + "gas_fee_cap": "25000000000", + "gas_tip_cap": "1000000000", + "effective_gas_price": "20000000000", + "cumulative_gas_used": 500000, + "tx_type": 2, + "revert_reason": null, + "l1_gas_used": null, + "l1_fee": null, + "policy_runs": [ + { + "on_chain": { + "target_contract_address": "0x1234567890123456789012345678901234567890", + "engine_address": "0x1234567890123456789012345678901234567890", + "method": { + "selector": "0xa9059cbb" + }, + "policies": [ + { + "address": "0x1234567890123456789012345678901234567890", + "version": 1, + "name": "Policy A", + "mapper_address": null, + "policy_state": null + } + ], + "params": { + "amount": 1000, + "recipient": "0x1234567890123456789012345678901234567890" + }, + "context": null, + "engine_default_behavior": "ALLOW", + "target_default_behavior": null, + "extractor_address": "0x1234567890123456789012345678901234567890", + "log_index": 0 + } + } + ] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Invalid request parameters", + "code": "BAD_REQUEST" + } + } + } + }, + "404": { + "description": "Transaction not found or has no policy engine evaluations in this organization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Resource not found", + "code": "NOT_FOUND" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "message": "Internal server error", + "code": "INTERNAL_ERROR" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Blockhash": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{64}$", + "description": "Block hash (0x followed by 64 hex characters)" + }, + "Bytes32": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{64}$", + "description": "32-byte hex string (0x followed by 64 hex characters)", + "example": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + }, + "ChainSelector": { + "type": "string", + "pattern": "^[0-9]{1,20}$", + "description": "Chain selector represented as a string to avoid JavaScript number precision loss (values can exceed 2^53). The underlying type is uint64.\n", + "example": "5009297550715157269" + }, + "CredentialData": { + "type": "object", + "description": "Credential associated with an identity", + "properties": { + "credential_type_id": { + "$ref": "#/components/schemas/Bytes32", + "description": "Type identifier for this credential (e.g., KYC_VERIFIED, ACCREDITED_INVESTOR)" + }, + "credential_registry": { + "$ref": "#/components/schemas/CredentialRegistryInfo", + "description": "The credential registry that issued this credential" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this credential expires (null if never expires)" + }, + "credential_data": { + "$ref": "#/components/schemas/HexBytes", + "nullable": true, + "description": "Hex-encoded credential data" + }, + "effective_from": { + "type": "string", + "format": "date-time", + "description": "When this credential became active" + }, + "effective_to": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this credential was revoked (null if still active)" + }, + "block_number": { + "type": "integer", + "description": "Block number when this credential was created" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash", + "description": "Block hash when this credential was created" + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash", + "description": "Transaction hash that created this credential" + } + }, + "required": [ + "credential_type_id", + "credential_registry", + "effective_from", + "block_number", + "block_hash", + "tx_hash" + ] + }, + "CredentialRegistryInfo": { + "type": "object", + "description": "Information about a credential registry", + "properties": { + "address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Credential registry contract address" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector", + "description": "Chain selector where the registry is deployed" + }, + "block_timestamp": { + "type": "string", + "format": "date-time", + "description": "Block timestamp when this registry was created" + }, + "block_number": { + "type": "integer", + "description": "Block number when this registry was created" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash", + "description": "Block hash when this registry was created" + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash", + "description": "Transaction hash that created this registry" + } + }, + "required": ["address", "chain_selector", "block_timestamp", "block_number", "block_hash", "tx_hash"] + }, + "DataValidatorEvaluation": { + "type": "object", + "description": "State of a data validator contract at the time of a policy run evaluation.", + "properties": { + "address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Data validator contract address" + }, + "version": { + "type": "integer", + "description": "Config version active at the evaluation block" + }, + "state": { + "$ref": "#/components/schemas/DataValidatorState", + "nullable": true, + "description": "Decoded data validator configuration active at the evaluation block. Null unless include_policy_details=true or state unavailable.\n" + } + }, + "required": ["address", "version"] + }, + "DataValidatorState": { + "type": "object", + "description": "Decoded data validator configuration as JSON. Structure varies by validator implementation and is passed through from the coordinator config log.\n", + "additionalProperties": true, + "example": { + "allowlist": [ + { + "item": "US" + } + ], + "denylist": [ + { + "item": "IR" + } + ] + } + }, + "DefaultBehavior": { + "type": "string", + "enum": ["ALLOW", "DENY"], + "description": "Default behavior when no policy explicitly allows or denies" + }, + "Error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Human-readable error message" + }, + "code": { + "type": "string", + "description": "Machine-readable error code (e.g., BAD_REQUEST, NOT_FOUND, INTERNAL_ERROR)" + }, + "request_id": { + "type": "string", + "description": "Unique request identifier (OTEL trace ID). Include this value when contacting support to help locate the request in logs and traces.\n", + "nullable": true + } + }, + "required": ["message", "code"] + }, + "EvmAddress": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "EVM address (0x followed by 40 hex characters)" + }, + "ExtractorInfo": { + "type": "object", + "description": "Extractor contract configuration with block metadata", + "properties": { + "address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Extractor contract address" + }, + "effective_from": { + "type": "string", + "format": "date-time", + "description": "When this extractor became active" + }, + "effective_to": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this extractor was replaced (null if still active)" + }, + "block_number": { + "type": "integer", + "description": "Block number when this extractor was set" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash", + "description": "Block hash when this extractor was set" + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash", + "description": "Transaction hash that set this extractor" + } + }, + "required": ["address", "effective_from", "block_number", "block_hash", "tx_hash"] + }, + "HealthResponse": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "ok" + } + }, + "required": ["status"] + }, + "HexBytes": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]*$", + "description": "Hex-encoded bytes (0x followed by hex characters)", + "example": "0x7b226e616d65223a22416c696365227d" + }, + "IdentityAddressInfo": { + "type": "object", + "description": "A wallet address registered to an identity in a specific registry", + "properties": { + "address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Wallet address" + }, + "effective_from": { + "type": "string", + "format": "date-time", + "description": "When this address registration became active" + }, + "effective_to": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this address registration was removed (null if still active)" + }, + "block_number": { + "type": "integer", + "description": "Block number when this registration was created" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash", + "description": "Block hash when this registration was created" + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash", + "description": "Transaction hash that created this registration" + } + }, + "required": ["address", "effective_from", "block_number", "block_hash", "tx_hash"] + }, + "IdentityData": { + "type": "object", + "description": "Identity representation with nested structure. A CCID (Cross-Chain ID) can be registered\nin multiple identity registries (across different chains) and can have multiple wallet\naddresses within each registry. Credentials are associated with the CCID itself.\n", + "properties": { + "org_id": { + "type": "string", + "description": "Organization that owns this identity. Present only for admin cross-org queries." + }, + "ccid": { + "$ref": "#/components/schemas/Bytes32", + "description": "Cross-Chain ID - unique identifier for the identity" + }, + "registries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IdentityRegistryEntry" + }, + "description": "Identity registries where this CCID is registered (across chains)" + }, + "credentials": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CredentialData" + }, + "description": "Credentials associated with this identity (null if include_credential_details=false)", + "nullable": true + } + }, + "required": ["ccid", "registries"] + }, + "IdentityListResponse": { + "type": "object", + "description": "Paginated list of identities. When no identities match the given filters, an empty array is returned with a 200 status (not 404).\n", + "properties": { + "identities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IdentityData" + } + }, + "next_page_token": { + "type": "string", + "description": "Opaque token to retrieve the next page of results. Null or absent when there are no more results. Pass this value as the page_token query parameter to fetch the next page.\n", + "nullable": true + } + }, + "required": ["identities"] + }, + "IdentityRegistryEntry": { + "type": "object", + "description": "Entry for a CCID within a specific identity registry", + "properties": { + "registry_address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Identity registry contract address" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector", + "description": "Chain selector where the registry is deployed" + }, + "addresses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IdentityAddressInfo" + }, + "description": "Wallet addresses registered for this CCID in this registry" + }, + "block_timestamp": { + "type": "string", + "format": "date-time", + "description": "Block timestamp when this registry was created" + }, + "block_number": { + "type": "integer", + "description": "Block number when this registry was created" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash", + "description": "Block hash when this registry was created" + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash", + "description": "Transaction hash that created this registry" + } + }, + "required": [ + "registry_address", + "chain_selector", + "addresses", + "block_timestamp", + "block_number", + "block_hash", + "tx_hash" + ] + }, + "MapperInfo": { + "type": "object", + "description": "Mapper contract configuration with block metadata", + "properties": { + "address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Mapper contract address" + }, + "effective_from": { + "type": "string", + "format": "date-time", + "description": "When this mapper became active" + }, + "effective_to": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this mapper was replaced (null if still active)" + }, + "block_number": { + "type": "integer", + "description": "Block number when this mapper was set" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash", + "description": "Block hash when this mapper was set" + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash", + "description": "Transaction hash that set this mapper" + } + }, + "required": ["address", "effective_from", "block_number", "block_hash", "tx_hash"] + }, + "MethodPolicy": { + "type": "object", + "description": "A policy attached to a protected method.\nNote: A policy only appears in this list if it has an active version at the as_of time.\nPolicies without versions are not included.\n", + "properties": { + "policy_address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Policy contract address" + }, + "policy_name": { + "type": "string", + "nullable": true, + "description": "Human-readable policy name (null if not set)" + }, + "parameter_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Parameter names this policy expects from the extractor (derived from policyParameterNames in contract)" + }, + "mapper": { + "$ref": "#/components/schemas/MapperInfo", + "nullable": true, + "description": "Mapper configuration for this policy (null if not configured)" + }, + "version": { + "type": "integer", + "description": "Policy version number (as of the requested as_of time)" + }, + "state": { + "$ref": "#/components/schemas/PolicyState", + "nullable": true, + "description": "Policy state/configuration (null if include_policy_details=false)" + }, + "effective_from": { + "type": "string", + "format": "date-time", + "description": "When this policy version became active" + }, + "effective_to": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this policy version was superseded (null if current version)" + }, + "block_number": { + "type": "integer", + "description": "Block number when this policy version was created" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash", + "description": "Block hash when this policy version was created" + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash", + "description": "Transaction hash that created this policy version" + } + }, + "required": [ + "policy_address", + "parameter_names", + "version", + "effective_from", + "block_number", + "block_hash", + "tx_hash" + ] + }, + "MethodSelector": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{8}$", + "description": "4-byte function selector (0x followed by 8 hex characters)", + "example": "0xa9059cbb" + }, + "OnChainPolicyRunData": { + "type": "object", + "description": "On-chain ACE policy engine evaluation details for a single PolicyRunComplete event", + "properties": { + "org_id": { + "type": "string", + "description": "Organization that owns this policy run. Present only for admin cross-org queries." + }, + "target_contract_address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Target contract address that was evaluated" + }, + "engine_address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Policy engine address that performed the evaluation" + }, + "method": { + "$ref": "#/components/schemas/PolicyRunMethod", + "description": "Function method that was evaluated" + }, + "policies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PolicyRunPolicyDetail" + }, + "nullable": true, + "description": "Policies attached to this selector at evaluation time (null if no policies exist)" + }, + "params": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Parameters extracted from the transaction calldata by the extractor" + }, + "context": { + "type": "string", + "nullable": true, + "description": "Additional context from the evaluation" + }, + "engine_default_behavior": { + "$ref": "#/components/schemas/DefaultBehavior", + "description": "Engine-level default behavior at time of evaluation" + }, + "target_default_behavior": { + "$ref": "#/components/schemas/DefaultBehavior", + "nullable": true, + "description": "Target-level default behavior at time of evaluation (null if unavailable)" + }, + "extractor_address": { + "$ref": "#/components/schemas/EvmAddress", + "nullable": true, + "description": "Extractor contract address active at time of evaluation (null if no extractor set for this selector)" + }, + "log_index": { + "type": "integer", + "description": "Log index of the evaluation event within the block" + } + }, + "required": ["target_contract_address", "engine_address", "engine_default_behavior", "method", "log_index"] + }, + "PageSize": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20, + "description": "Maximum number of results to return per page (1-100, default 20). The actual number of results may be less than page_size if fewer results remain. When next_page_token is null in the response, there are no more results.\n" + }, + "PermitListResponse": { + "type": "object", + "required": ["permits"], + "properties": { + "permits": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StoredPermit" + } + }, + "next_page_token": { + "type": "string", + "nullable": true, + "description": "Opaque pagination token; absent or null when no more pages." + } + } + }, + "PolicyData": { + "type": "object", + "properties": { + "org_id": { + "type": "string", + "description": "Organization that owns this policy. Present only for admin cross-org queries." + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "address": { + "$ref": "#/components/schemas/EvmAddress" + }, + "owner": { + "$ref": "#/components/schemas/EvmAddress" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Human-readable policy name (null if not set)" + }, + "description": { + "type": "string", + "nullable": true, + "description": "Policy description (null if not set)" + }, + "policy_engine_address": { + "$ref": "#/components/schemas/EvmAddress" + }, + "version": { + "type": "integer", + "description": "Version number (latest unless as_of is specified)" + }, + "state": { + "$ref": "#/components/schemas/PolicyState", + "description": "Policy state (null if include_policy_details=false)", + "nullable": true + }, + "effective_from": { + "type": "string", + "format": "date-time", + "description": "When this policy version became active" + }, + "effective_to": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this policy version was superseded (null if current version)" + }, + "block_number": { + "type": "integer", + "description": "Block number when this version was created" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash", + "description": "Block hash when this version was created" + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash", + "description": "Transaction hash that created this version" + } + }, + "required": [ + "chain_selector", + "address", + "owner", + "policy_engine_address", + "version", + "effective_from", + "block_number", + "block_hash", + "tx_hash" + ] + }, + "PolicyListResponse": { + "type": "object", + "description": "Paginated list of policies. When no policies match the given filters, an empty array is returned with a 200 status (not 404).\n", + "properties": { + "policies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PolicyData" + } + }, + "next_page_token": { + "type": "string", + "description": "Opaque token to retrieve the next page of results. Null or absent when there are no more results. Pass this value as the page_token query parameter to fetch the next page.\n", + "nullable": true + } + }, + "required": ["policies"] + }, + "PolicyRunCredentialSource": { + "type": "object", + "description": "A credential source configured on a validator policy at the time of a policy run.", + "properties": { + "identity_registry": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Identity registry address" + }, + "credential_registry": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Credential registry address" + }, + "credential_type_id": { + "$ref": "#/components/schemas/Bytes32", + "nullable": true, + "description": "Credential type ID (null when not restricted to a specific type)" + }, + "data_validator": { + "$ref": "#/components/schemas/DataValidatorEvaluation", + "nullable": true, + "description": "Data validator state at time of evaluation; null when no validator is configured for this source." + } + }, + "required": ["identity_registry", "credential_registry"] + }, + "PolicyRunMethod": { + "type": "object", + "description": "Function method information for a policy run", + "properties": { + "selector": { + "$ref": "#/components/schemas/MethodSelector", + "description": "4-byte function selector (e.g. 0xa9059cbb)" + } + }, + "required": ["selector"] + }, + "PolicyRunPolicyDetail": { + "type": "object", + "description": "Details of a single policy attached to the evaluated selector at the time of the policy run.\nNote: per-policy individual result (ALLOWED/DENIED/CONTINUE), denial_reason, and mapped_params\nare not available from the on-chain PolicyRunComplete event.\n", + "properties": { + "address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Policy contract address" + }, + "version": { + "type": "integer", + "nullable": true, + "description": "Policy version active at time of evaluation" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Human-readable policy name (null if not set)" + }, + "mapper_address": { + "$ref": "#/components/schemas/EvmAddress", + "nullable": true, + "description": "Mapper contract address active at time of evaluation (null if not configured)" + }, + "policy_state": { + "$ref": "#/components/schemas/PolicyState", + "nullable": true, + "description": "Policy state/configuration at time of evaluation (null if include_policy_details=false or unavailable)" + }, + "credential_sources": { + "type": "array", + "nullable": true, + "items": { + "$ref": "#/components/schemas/PolicyRunCredentialSource" + }, + "description": "Credential sources configured on this policy at evaluation time. Present only for CredentialRegistryIdentityValidatorPolicy and GroupedIdentityValidatorPolicy; null for all other policy types. data_validator.state within each source is populated only when include_policy_details=true.\n" + } + }, + "required": ["address"] + }, + "PolicyState": { + "type": "object", + "description": "Policy state as JSON. Structure varies by policy type and is passed through from the on-chain policy contract. Field names within the state object are determined by the smart contract and may use camelCase (e.g., maxAmount) rather than the snake_case convention used by the rest of this API.\n", + "additionalProperties": true, + "example": { + "maxAmount": 10000, + "minAmount": 100 + } + }, + "ProtectedMethod": { + "type": "object", + "description": "A method on the target contract that is protected by policies", + "properties": { + "method_selector": { + "$ref": "#/components/schemas/MethodSelector", + "description": "4-byte function selector (e.g., 0xa9059cbb for transfer)" + }, + "extractor": { + "$ref": "#/components/schemas/ExtractorInfo", + "nullable": true, + "description": "Extractor configuration for this selector (null if not configured)" + }, + "policies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MethodPolicy" + }, + "description": "Policies attached to this method (order is significant - evaluated in order). Empty array if no policies configured." + } + }, + "required": ["method_selector", "policies"] + }, + "RegistryType": { + "type": "string", + "enum": ["credential", "identity"], + "description": "Registry tenancy type for the registry_address filter" + }, + "RegistryUsageEvent": { + "type": "object", + "description": "One proven IDV registry usage from a PolicyRunComplete-backed run. consumer_org_id is omitted.\n", + "required": [ + "registry_type", + "registry_address", + "chain_selector", + "ccid", + "tx_hash", + "block_number", + "block_hash", + "block_timestamp", + "log_index", + "target_contract_address", + "policy_engine_address", + "function_selector" + ], + "properties": { + "org_id": { + "type": "string", + "description": "Organization that owns this registry. Present only for admin cross-org queries." + }, + "registry_type": { + "$ref": "#/components/schemas/RegistryType" + }, + "registry_address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Registry address from the request filter (lowercase)" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "ccid": { + "$ref": "#/components/schemas/Bytes32", + "description": "Lowercase hex bytes32 (0x-prefixed); aligns with reporting CCID conventions." + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash" + }, + "block_number": { + "type": "integer" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash" + }, + "block_timestamp": { + "type": "string", + "format": "date-time" + }, + "log_index": { + "type": "integer", + "description": "Policy run / PolicyRunComplete log index (disambiguate multiple runs per tx)." + }, + "target_contract_address": { + "$ref": "#/components/schemas/EvmAddress" + }, + "policy_engine_address": { + "$ref": "#/components/schemas/EvmAddress" + }, + "function_selector": { + "$ref": "#/components/schemas/MethodSelector" + }, + "matched_policy_address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Policy whose policy_versions.state contained the matching credentialSources element." + }, + "matched_identity_registry": { + "$ref": "#/components/schemas/EvmAddress" + }, + "matched_credential_registry": { + "$ref": "#/components/schemas/EvmAddress" + }, + "credential_type_ids": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Bytes32" + }, + "description": "Credential type IDs declared on the matched policy credential source(s) for this identity registry / credential registry pair. PolicyRunComplete does not carry the specific credential type that satisfied validation, so all acceptable credential types are listed at the time of the policy run.\n" + } + } + }, + "RegistryUsageEventListResponse": { + "type": "object", + "required": ["events"], + "properties": { + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RegistryUsageEvent" + } + }, + "next_page_token": { + "type": "string", + "nullable": true, + "description": "Opaque pagination token; absent or null when no more pages." + } + } + }, + "StoredPermit": { + "type": "object", + "description": "One PermitStored event for metering off-chain workflow permits.", + "required": [ + "permit_id", + "policy_address", + "chain_selector", + "tx_hash", + "block_number", + "block_hash", + "block_timestamp", + "log_index" + ], + "properties": { + "org_id": { + "type": "string", + "description": "Organization that owns this permit's policy. Present only for admin cross-org queries." + }, + "permit_id": { + "$ref": "#/components/schemas/Bytes32" + }, + "policy_address": { + "$ref": "#/components/schemas/EvmAddress" + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector" + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash" + }, + "block_number": { + "type": "integer" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash" + }, + "block_timestamp": { + "type": "string", + "format": "date-time" + }, + "log_index": { + "type": "integer", + "description": "PermitStored log index within the block." + } + } + }, + "TargetData": { + "type": "object", + "description": "Target contract representation. A target is a contract protected by one or more policy engines.\nEach target can be attached to multiple policy engines, and each attachment can have\nprotected methods with associated policies.\n", + "properties": { + "org_id": { + "type": "string", + "description": "Organization that owns this target. Present only for admin cross-org queries." + }, + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector", + "description": "Chain selector where the target is deployed" + }, + "address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Target contract address" + }, + "policy_engines": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TargetPolicyEngineAttachment" + }, + "description": "Policy engines this target is attached to" + } + }, + "required": ["chain_selector", "address", "policy_engines"] + }, + "TargetListResponse": { + "type": "object", + "description": "Paginated list of targets. When no targets match the given filters, an empty array is returned with a 200 status (not 404).\n", + "properties": { + "targets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TargetData" + } + }, + "next_page_token": { + "type": "string", + "description": "Opaque token to retrieve the next page of results. Null or absent when there are no more results. Pass this value as the page_token query parameter to fetch the next page.\n", + "nullable": true + } + }, + "required": ["targets"] + }, + "TargetPolicyEngineAttachment": { + "type": "object", + "description": "Information about a target's attachment to a policy engine", + "properties": { + "policy_engine_address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Policy engine contract address" + }, + "attached_at": { + "type": "string", + "format": "date-time", + "description": "When this target was attached to the policy engine" + }, + "detached_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this target was detached (null if still attached)" + }, + "engine_default_behavior": { + "$ref": "#/components/schemas/DefaultBehavior", + "description": "Engine-level default behavior when no policy matches (ALLOW or DENY)" + }, + "target_default_behavior": { + "$ref": "#/components/schemas/DefaultBehavior", + "description": "Target-level default behavior override (null if not configured)", + "nullable": true + }, + "protected_methods": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProtectedMethod" + }, + "description": "Methods protected by policies (null if no methods configured)", + "nullable": true + }, + "block_number": { + "type": "integer", + "description": "Block number when this attachment was created" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash", + "description": "Block hash when this attachment was created" + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash", + "description": "Transaction hash that created this attachment" + } + }, + "required": [ + "policy_engine_address", + "attached_at", + "engine_default_behavior", + "block_number", + "block_hash", + "tx_hash" + ] + }, + "TransactionData": { + "type": "object", + "description": "On-chain transaction that triggered at least one ACE policy engine\nevaluation (PolicyRunComplete event). Every returned transaction is\nguaranteed to have one or more policy runs. Policy state detail is\nincluded only when include_policy_details=true.\n", + "properties": { + "chain_selector": { + "$ref": "#/components/schemas/ChainSelector", + "description": "Chain selector where the transaction was executed" + }, + "tx_hash": { + "$ref": "#/components/schemas/TransactionHash", + "description": "Transaction hash" + }, + "block_number": { + "type": "integer", + "description": "Block number containing the transaction" + }, + "block_hash": { + "$ref": "#/components/schemas/Blockhash", + "description": "Block hash containing the transaction" + }, + "block_timestamp": { + "type": "string", + "format": "date-time", + "description": "Timestamp of the block containing the transaction" + }, + "from_address": { + "$ref": "#/components/schemas/EvmAddress", + "description": "Sender address" + }, + "to_address": { + "type": "string", + "nullable": true, + "description": "Recipient address (null for contract creation transactions)" + }, + "value": { + "type": "string", + "description": "Transaction value in wei (as string to avoid precision loss)" + }, + "data": { + "type": "string", + "nullable": true, + "description": "Transaction input data (hex-encoded)" + }, + "gas_used": { + "type": "integer", + "description": "Gas consumed by the transaction" + }, + "gas_limit": { + "type": "integer", + "description": "Gas limit set for the transaction" + }, + "gas_price": { + "type": "string", + "nullable": true, + "description": "Gas price in wei (as string)" + }, + "gas_fee_cap": { + "type": "string", + "nullable": true, + "description": "Maximum fee per gas (EIP-1559, as string)" + }, + "gas_tip_cap": { + "type": "string", + "nullable": true, + "description": "Maximum priority fee per gas (EIP-1559, as string)" + }, + "effective_gas_price": { + "type": "string", + "nullable": true, + "description": "Effective gas price in wei (as string)" + }, + "cumulative_gas_used": { + "type": "integer", + "nullable": true, + "description": "Cumulative gas used in the block up to this transaction" + }, + "tx_type": { + "type": "integer", + "nullable": true, + "description": "Transaction type (0=legacy, 1=access list, 2=EIP-1559)" + }, + "revert_reason": { + "type": "string", + "nullable": true, + "description": "Revert reason if transaction reverted (null if succeeded or not available)" + }, + "l1_gas_used": { + "type": "integer", + "nullable": true, + "description": "L1 gas used (for L2 chains, null otherwise)" + }, + "l1_fee": { + "type": "string", + "nullable": true, + "description": "L1 fee in wei (for L2 chains, as string, null otherwise)" + }, + "policy_runs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionPolicyRunData" + }, + "description": "ACE policy engine evaluations (PolicyRunComplete events) for this transaction" + } + }, + "required": [ + "chain_selector", + "tx_hash", + "block_number", + "block_hash", + "block_timestamp", + "from_address", + "value", + "gas_used", + "gas_limit", + "policy_runs" + ] + }, + "TransactionHash": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{64}$", + "description": "Transaction hash (0x followed by 64 hex characters)" + }, + "TransactionListResponse": { + "type": "object", + "properties": { + "transactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionData" + } + }, + "next_page_token": { + "type": "string", + "description": "Opaque token to retrieve the next page of results. Null or absent when there are no more results. Pass this value as the page_token query parameter to fetch the next page.\n", + "nullable": true + } + }, + "required": ["transactions"] + }, + "TransactionPolicyRunData": { + "type": "object", + "description": "A policy engine evaluation for a transaction. Contains nested on-chain evaluation\ndetails. Off-chain evaluation is not yet implemented.\n", + "properties": { + "on_chain": { + "$ref": "#/components/schemas/OnChainPolicyRunData", + "description": "On-chain policy engine evaluation details" + } + }, + "required": ["on_chain"] + } + }, + "parameters": { + "AdminOrgId": { + "name": "org_id", + "in": "query", + "description": "Admin-only filter to scope results to a specific organization. Ignored for tenant requests.", + "required": false, + "schema": { + "type": "string", + "example": "org_SD46LSQkJbnaiBTg" + } + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "Authorization", + "description": "API key authentication. Format: `Apikey `" + } + } + }, + "tags": [ + { + "name": "Health Check" + }, + { + "name": "Policies" + }, + { + "name": "Identities" + }, + { + "name": "Targets" + }, + { + "name": "Transactions" + }, + { + "name": "Permits" + }, + { + "name": "Registry usage" + } + ] +} diff --git a/public/images/ace/account-setup/add-ace-wallets.webp b/public/images/ace/account-setup/add-ace-wallets.webp new file mode 100644 index 00000000000..5159d50e354 Binary files /dev/null and b/public/images/ace/account-setup/add-ace-wallets.webp differ diff --git a/public/images/ace/account-setup/api-key-1.webp b/public/images/ace/account-setup/api-key-1.webp new file mode 100644 index 00000000000..9d768b65d6f Binary files /dev/null and b/public/images/ace/account-setup/api-key-1.webp differ diff --git a/public/images/ace/account-setup/api-key-2.webp b/public/images/ace/account-setup/api-key-2.webp new file mode 100644 index 00000000000..f4792032ed7 Binary files /dev/null and b/public/images/ace/account-setup/api-key-2.webp differ diff --git a/public/images/ace/account-setup/organization-id.webp b/public/images/ace/account-setup/organization-id.webp new file mode 100644 index 00000000000..5675089e415 Binary files /dev/null and b/public/images/ace/account-setup/organization-id.webp differ diff --git a/public/images/ace/identity-manager-getting-started/registry-1.webp b/public/images/ace/identity-manager-getting-started/registry-1.webp new file mode 100644 index 00000000000..34ab39b0bbc Binary files /dev/null and b/public/images/ace/identity-manager-getting-started/registry-1.webp differ diff --git a/public/images/ace/managing-policies/policy-implementations-list.webp b/public/images/ace/managing-policies/policy-implementations-list.webp new file mode 100644 index 00000000000..3e05196d20e Binary files /dev/null and b/public/images/ace/managing-policies/policy-implementations-list.webp differ diff --git a/public/images/ace/policy-engines/policy-engine-settings-2.webp b/public/images/ace/policy-engines/policy-engine-settings-2.webp new file mode 100644 index 00000000000..1d16cf37a2f Binary files /dev/null and b/public/images/ace/policy-engines/policy-engine-settings-2.webp differ diff --git a/public/images/ace/policy-engines/policy-engine-view-1.webp b/public/images/ace/policy-engines/policy-engine-view-1.webp new file mode 100644 index 00000000000..f95dc058e2c Binary files /dev/null and b/public/images/ace/policy-engines/policy-engine-view-1.webp differ diff --git a/public/images/ace/signing-model-sva-operations.png b/public/images/ace/signing-model-sva-operations.png new file mode 100644 index 00000000000..0fbca594185 Binary files /dev/null and b/public/images/ace/signing-model-sva-operations.png differ diff --git a/public/images/ace/signing-model-sva-setup.png b/public/images/ace/signing-model-sva-setup.png new file mode 100644 index 00000000000..c268d8ecc45 Binary files /dev/null and b/public/images/ace/signing-model-sva-setup.png differ diff --git a/src/components/ACE/PolicyInstanceBuilder/PolicyInstanceBuilder.module.css b/src/components/ACE/PolicyInstanceBuilder/PolicyInstanceBuilder.module.css new file mode 100644 index 00000000000..858b8e54d79 --- /dev/null +++ b/src/components/ACE/PolicyInstanceBuilder/PolicyInstanceBuilder.module.css @@ -0,0 +1,285 @@ +.container { + margin: var(--space-4x, 1.5rem) 0; +} + +.selectorCard { + display: flex; + flex-direction: column; + gap: 0.75rem; + padding: 24px; + background: var(--theme-bg-offset, #f7f7f8); + border: 1px solid var(--theme-divider, #e2e2e2); + border-radius: 10px; + margin-bottom: var(--space-6x, 2rem); +} + +.label { + font-weight: 600; + font-size: 1rem; + color: var(--theme-text); +} + +.select { + appearance: none; + width: 100%; + max-width: 520px; + padding: 12px 40px 12px 16px; + font-family: var(--font-body); + font-size: 1rem; + color: var(--theme-text); + background-color: var(--theme-bg); + border: 1.5px solid var(--theme-divider, #e2e2e2); + border-radius: 8px; + cursor: pointer; + transition: + border-color 0.15s ease, + box-shadow 0.15s ease; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' fill='none'%3E%3Cpath d='M1 1.5l5 5 5-5' stroke='%23666' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 14px center; +} + +.select:hover { + border-color: var(--blue-500, #4c6fff); +} + +.select:focus { + outline: none; + border-color: var(--blue-500, #4c6fff); + box-shadow: 0 0 0 3px rgba(55, 91, 210, 0.15); +} + +.details { + animation: fadeIn 0.2s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.description { + color: var(--theme-text-light); + margin-bottom: var(--space-4x, 1.5rem); + line-height: 1.6; +} + +.idRow { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 12px 16px; + background: var(--theme-bg-offset, #f7f7f8); + border: 1px solid var(--theme-divider, #e2e2e2); + border-radius: 8px; + margin-bottom: var(--space-4x, 1.5rem); + flex-wrap: wrap; +} + +.idLabel { + font-weight: 600; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.03em; + color: var(--theme-text-light); + white-space: nowrap; +} + +.idValue { + font-family: var(--font-mono, monospace); + font-size: 0.85rem; + color: var(--theme-text); + word-break: break-all; +} + +.copyButton { + display: inline-flex; + align-items: center; + padding: 4px 10px; + font-family: var(--font-body); + font-size: 0.75rem; + font-weight: 500; + color: var(--blue-600, #375bd2); + background: transparent; + border: 1px solid var(--blue-600, #375bd2); + border-radius: 4px; + cursor: pointer; + transition: + background 0.15s ease, + color 0.15s ease; + white-space: nowrap; + flex-shrink: 0; +} + +.copyButton:hover { + background: var(--blue-600, #375bd2); + color: #fff; +} + +.sectionTitle { + font-size: 1rem; + font-weight: 600; + margin-top: var(--space-4x, 1.5rem); + margin-bottom: 0.75rem; + color: var(--theme-text); +} + +.tableWrapper { + overflow-x: auto; + margin-bottom: var(--space-4x, 1.5rem); +} + +.table { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; +} + +.table th { + text-align: left; + padding: 10px 12px; + font-weight: 600; + color: var(--theme-text-light); + border-bottom: 2px solid var(--theme-divider, #e2e2e2); + white-space: nowrap; +} + +.table td { + padding: 10px 12px; + border-bottom: 1px solid var(--theme-divider, #e2e2e2); + color: var(--theme-text); + vertical-align: top; +} + +.table td code { + font-size: 0.8rem; + padding: 2px 5px; + background: var(--theme-bg-offset, #f7f7f8); + border-radius: 3px; +} + +.codeBlock { + border: 1px solid var(--theme-divider, #e2e2e2); + border-radius: 8px; + overflow: hidden; + margin-bottom: var(--space-4x, 1.5rem); +} + +.codeHeader { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 12px; + background: var(--theme-bg-offset, #f0f0f2); + border-bottom: 1px solid var(--theme-divider, #e2e2e2); + font-family: var(--font-body); + font-size: 0.75rem; + font-weight: 500; + color: var(--theme-text-light); + text-transform: uppercase; + letter-spacing: 0.03em; +} + +.codeContent { + padding: 16px; + background: var(--theme-bg, #fff); + font-family: var(--font-mono, monospace); + font-size: 0.8rem; + line-height: 1.6; + color: var(--theme-text); + white-space: pre; + overflow-x: auto; +} + +.learnMore { + font-size: 0.875rem; + color: var(--theme-text-light); + margin-top: 0.5rem; +} + +.learnMore a { + color: var(--blue-600, #375bd2); + text-decoration: none; + font-weight: 500; +} + +.learnMore a:hover { + text-decoration: underline; +} + +/* Dark theme */ +:global(.theme-dark) .selectorCard { + background: rgba(255, 255, 255, 0.04); + border-color: #3a3a3a; +} + +:global(.theme-dark) .select { + border-color: #3a3a3a; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' fill='none'%3E%3Cpath d='M1 1.5l5 5 5-5' stroke='%23aaa' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); +} + +:global(.theme-dark) .idRow { + background: rgba(255, 255, 255, 0.04); + border-color: #3a3a3a; +} + +:global(.theme-dark) .table th { + border-bottom-color: #3a3a3a; +} + +:global(.theme-dark) .table td { + border-bottom-color: #3a3a3a; +} + +:global(.theme-dark) .table td code { + background: rgba(255, 255, 255, 0.06); +} + +:global(.theme-dark) .copyButton { + color: var(--blue-400, #6d9eff); + border-color: var(--blue-400, #6d9eff); +} + +:global(.theme-dark) .copyButton:hover { + background: var(--blue-400, #6d9eff); + color: #1a1a1a; +} + +:global(.theme-dark) .codeBlock { + border-color: #3a3a3a; +} + +:global(.theme-dark) .codeHeader { + background: rgba(255, 255, 255, 0.06); + border-bottom-color: #3a3a3a; +} + +:global(.theme-dark) .codeContent { + background: var(--theme-bg, #1a1a1a); +} + +:global(.theme-dark) .learnMore a { + color: var(--blue-400, #6d9eff); +} + +@media (max-width: 640px) { + .idRow { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + + .select { + max-width: 100%; + } + + .selectorCard { + padding: 16px; + } +} diff --git a/src/components/ACE/PolicyInstanceBuilder/PolicyInstanceBuilder.tsx b/src/components/ACE/PolicyInstanceBuilder/PolicyInstanceBuilder.tsx new file mode 100644 index 00000000000..2e5c2d5cb1d --- /dev/null +++ b/src/components/ACE/PolicyInstanceBuilder/PolicyInstanceBuilder.tsx @@ -0,0 +1,144 @@ +import { useState, useCallback } from "react" +import styles from "./PolicyInstanceBuilder.module.css" +import policyData from "../../../data/ace-policy-implementations.json" with { type: "json" } + +interface ConfigField { + name: string + type: string + description: string + requiredAtCreation: boolean +} + +interface PolicyImplementation { + id: string + name: string + apiName: string + description: string + libraryUrl: string + configFields: ConfigField[] + exampleConfig: Record +} + +const policies = policyData as PolicyImplementation[] + +function generateCurl(policy: PolicyImplementation): string { + const hasInitialConfig = Object.keys(policy.exampleConfig).length > 0 + const configJson = hasInitialConfig + ? JSON.stringify(policy.exampleConfig, null, 6).replace(/\n/g, "\n ") + : "{}" + + return `curl -X POST https://ace.api.chain.link/v1/policies \\ + -H "Content-Type: application/json" \\ + -H "Authorization: Apikey " \\ + -d '{ + "name": "My ${policy.name} Instance", + "description": "Description for this policy instance", + "policy_implementation_id": "${policy.id}", + "policy_engine_id": "", + "onchain_policies": [ + { + "chain_selector": "16015286601757825753", + "initial_config": ${configJson} + } + ] + }'` +} + +function CopyButton({ text }: { text: string }) { + const [copied, setCopied] = useState(false) + + const handleCopy = useCallback(() => { + navigator.clipboard.writeText(text).then(() => { + setCopied(true) + setTimeout(() => setCopied(false), 2000) + }) + }, [text]) + + return ( + + ) +} + +export default function PolicyInstanceBuilder() { + const [selectedId, setSelectedId] = useState("") + + const selected = policies.find((p) => p.id === selectedId) + + return ( +
+
+ + +
+ + {selected && ( +
+

{selected.description}

+ +
+ Implementation ID + {selected.id} + +
+ +

Configuration fields

+
+ + + + + + + + + + + {selected.configFields.map((field) => ( + + + + + + + ))} + +
FieldTypeRequired at creationDescription
+ {field.name} + + {field.type} + {field.requiredAtCreation ? "Yes" : "No"}{field.description}
+
+ +

Example curl command

+
+
+ bash + +
+
{generateCurl(selected)}
+
+ +

+ Learn more about this policy's runtime behavior: {selected.name} reference +

+
+ )} +
+ ) +} diff --git a/src/components/LeftSidebar/RecursiveSidebar.astro b/src/components/LeftSidebar/RecursiveSidebar.astro index 3e42fb21aff..df20459814d 100644 --- a/src/components/LeftSidebar/RecursiveSidebar.astro +++ b/src/components/LeftSidebar/RecursiveSidebar.astro @@ -131,8 +131,8 @@ function shouldExpandSection(item: SectionContent, currentPage: string): boolean aria-current={ isCurrentPageMatch(item.url, currentPage, item.highlightAsCurrent) ? "page" : undefined } - target={isExternalUrl(item.url) ? "_blank" : undefined} - rel={isExternalUrl(item.url) ? "noopener noreferrer" : undefined} + target={isExternalUrl(item.url) || item.openInNewTab ? "_blank" : undefined} + rel={isExternalUrl(item.url) || item.openInNewTab ? "noopener noreferrer" : undefined} > {item.title} @@ -150,8 +150,8 @@ function shouldExpandSection(item: SectionContent, currentPage: string): boolean class={`${styles.navLink} ${isCurrentPageMatch(item.url, currentPage, item.highlightAsCurrent) ? styles.active : ""}`} href={buildHref(item.url)} aria-current={isCurrentPageMatch(item.url, currentPage, item.highlightAsCurrent) ? "page" : undefined} - target={isExternalUrl(item.url) ? "_blank" : undefined} - rel={isExternalUrl(item.url) ? "noopener noreferrer" : undefined} + target={isExternalUrl(item.url) || item.openInNewTab ? "_blank" : undefined} + rel={isExternalUrl(item.url) || item.openInNewTab ? "noopener noreferrer" : undefined} > {item.title} diff --git a/src/components/MethodTabs/MethodTabs.astro b/src/components/MethodTabs/MethodTabs.astro new file mode 100644 index 00000000000..95b8b6d676a --- /dev/null +++ b/src/components/MethodTabs/MethodTabs.astro @@ -0,0 +1,12 @@ +--- +import { MethodTabsClient } from "./MethodTabsClient" +--- + + + + + + + + + diff --git a/src/components/MethodTabs/MethodTabs.module.css b/src/components/MethodTabs/MethodTabs.module.css new file mode 100644 index 00000000000..24cc48043c2 --- /dev/null +++ b/src/components/MethodTabs/MethodTabs.module.css @@ -0,0 +1,86 @@ +.wrapper { + margin: var(--space-4x) 0; +} + +.tabBar { + display: flex; + gap: 2px; + margin-bottom: var(--space-4x); + border-bottom: 1px solid #e2e2e2; +} + +.tab { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 10px 18px; + border: none; + background: transparent; + color: var(--theme-text-light); + font-family: var(--font-body); + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: color 0.2s ease; + white-space: nowrap; + line-height: 1.4; + margin-bottom: -1px; + border-bottom: 2px solid transparent; +} + +.tab:hover { + color: var(--theme-text); +} + +.tabActive { + color: var(--blue-600, #375bd2); + border-bottom-color: var(--blue-600, #375bd2); +} + +.tabIcon { + display: inline-flex; + align-items: center; + flex-shrink: 0; + width: 16px; + height: 16px; +} + +.tabIcon svg { + width: 16px; + height: 16px; +} + +.panelContainer { + position: relative; +} + +.panelUI, +.panelAPI { + visibility: visible; +} + +:global(html[data-method-tab="api"]) .panelUI, +:global(html[data-method-tab="ui"]) .panelAPI { + visibility: hidden; + position: absolute; + top: 0; + left: 0; + width: 100%; + pointer-events: none; +} + +:global(.theme-dark) .tabBar { + border-bottom-color: #3a3a3a; +} + +:global(.theme-dark) .tabActive { + color: var(--blue-400, #6d9eff); + border-bottom-color: var(--blue-400, #6d9eff); +} + +@media (max-width: 480px) { + .tab { + flex: 1; + justify-content: center; + } +} diff --git a/src/components/MethodTabs/MethodTabsClient.tsx b/src/components/MethodTabs/MethodTabsClient.tsx new file mode 100644 index 00000000000..4a250ebf908 --- /dev/null +++ b/src/components/MethodTabs/MethodTabsClient.tsx @@ -0,0 +1,111 @@ +/** @jsxImportSource preact */ +import type { ComponentChild } from "preact" +import { useRef, useEffect } from "preact/hooks" +import { useTabState } from "../Tabs/useTabState.ts" +import styles from "./MethodTabs.module.css" +import { clsx } from "~/lib/clsx/clsx.ts" + +const UIIcon = () => ( + + + + + + + +) + +const APIIcon = () => ( + + + + + + + +) + +type Props = { + "panel.ui"?: ComponentChild + "panel.api"?: ComponentChild +} + +function switchMethodTab(newTab: string, anchorEl: HTMLElement) { + const topBefore = anchorEl.getBoundingClientRect().top + + document.documentElement.dataset.methodTab = newTab + + const topAfter = anchorEl.getBoundingClientRect().top + + const delta = topAfter - topBefore + if (delta !== 0) { + window.scrollBy(0, delta) + } +} + +export function MethodTabsClient(props: Props) { + const [curr, setCurr] = useTabState("api", "methodPreference") + const wrapperRef = useRef(null) + + useEffect(() => { + document.documentElement.dataset.methodTab ??= curr + }, []) + + const handleSwitch = (tab: "ui" | "api") => { + if (tab === curr) return + switchMethodTab(tab, wrapperRef.current!) + setCurr(tab) + } + + return ( +
+
+ + +
+ +
+
+ {props["panel.ui"]} +
+
+ {props["panel.api"]} +
+
+
+ ) +} diff --git a/src/components/index.ts b/src/components/index.ts index 0738a698246..625331b82a2 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -16,6 +16,7 @@ export { default as ReleaseNotes } from "./ReleaseNotes.astro" export { default as Icon } from "./Icon.astro" export { default as ClickToZoom } from "./ClickToZoom.astro" export { default as PackageManagerTabs } from "./Tabs/PackageManagerTabs.astro" +export { default as MethodTabs } from "./MethodTabs/MethodTabs.astro" export { default as Accordion } from "./Quickstart/Accordion/Accordion.astro" export { default as PageTabs } from "./PageTabs.astro" export { default as SideBySideCode } from "./SideBySideCode/SideBySideCode.astro" diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index 1f4463c96d9..ade2a33c7cc 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -30,6 +30,7 @@ export type SectionContent = { children?: SectionContent[] isCollapsible?: boolean chainTypes?: ChainType[] + openInNewTab?: boolean } /** @@ -82,6 +83,7 @@ export const SIDEBAR: Partial> = { { title: "Architecture", url: "ace/concepts/architecture" }, { title: "Key Terms", url: "ace/concepts/key-terms" }, { title: "Signing & Ownership Model", url: "ace/concepts/signing-ownership" }, + { title: "Security Model", url: "ace/concepts/security" }, { title: "Policy Management", url: "ace/concepts/policy-management", @@ -94,7 +96,119 @@ export const SIDEBAR: Partial> = { }, { section: "Getting Started", - contents: [{ title: "Get Access", url: "ace/getting-started" }], + contents: [ + { title: "Getting Started with ACE", url: "ace/getting-started" }, + { title: "Account Setup", url: "ace/getting-started/account-setup" }, + { title: "Policy Manager Quick Start", url: "ace/getting-started/policy-manager" }, + { title: "Identity Manager Quick Start", url: "ace/getting-started/identity-manager" }, + ], + }, + { + section: "Guides", + contents: [ + { + title: "Policy Manager", + url: "ace/guides/policy-manager", + children: [ + { + title: "Making Your Contract ACE-Compatible", + url: "ace/guides/policy-manager/contracts/ace-compatible", + children: [ + { + title: "Building a New Contract", + url: "ace/guides/policy-manager/contracts/new-contract", + children: [ + { + title: "Building an ERC-20 Token", + url: "ace/guides/policy-manager/contracts/erc20-token", + }, + { + title: "Building an ERC-3643 Token", + url: "ace/guides/policy-manager/contracts/erc3643-token", + }, + ], + }, + { + title: "Upgrading Existing Contracts", + url: "ace/guides/policy-manager/contracts/upgrade-existing", + }, + { + title: "Security Considerations", + url: "ace/guides/policy-manager/contracts/security-considerations", + }, + ], + }, + { + title: "Managing Policy Engines", + url: "ace/guides/policy-manager/manage-engines", + }, + { + title: "Managing Targets", + url: "ace/guides/policy-manager/manage-targets", + }, + { + title: "Managing Policies", + url: "ace/guides/policy-manager/manage-policies", + }, + { + title: "Protecting Target Functions", + url: "ace/guides/policy-manager/manage-protections", + }, + { + title: "Managing Data Validators", + url: "ace/guides/policy-manager/manage-data-validators", + }, + { + title: "Custom Policies", + url: "ace/guides/policy-manager/custom-policies", + }, + { + title: "Offchain Policies", + url: "ace/guides/policy-manager/offchain-policies", + children: [ + { + title: "Managing Offchain Policies (MVP)", + url: "ace/guides/policy-manager/offchain-policies/manage-offchain-policies", + }, + { + title: "Requesting Offchain Permits", + url: "ace/guides/policy-manager/offchain-policies/request-offchain-permits", + }, + { + title: "Granting Evaluation Access", + url: "ace/guides/policy-manager/offchain-policies/grant-evaluation-access", + }, + ], + }, + ], + }, + { + title: "Identity Manager", + url: "ace/guides/identity-manager", + children: [ + { + title: "Managing Registries", + url: "ace/guides/identity-manager/manage-registries", + }, + { + title: "Managing Identities", + url: "ace/guides/identity-manager/manage-identities", + }, + { + title: "Managing Credential Types", + url: "ace/guides/identity-manager/manage-credential-types", + }, + { + title: "Managing Credentials", + url: "ace/guides/identity-manager/manage-credentials", + }, + { + title: "External Registries", + url: "ace/guides/identity-manager/external-registries", + }, + ], + }, + ], }, { section: "Reference", @@ -146,10 +260,23 @@ export const SIDEBAR: Partial> = { }, { title: "APIs", + url: "ace/reference/apis", children: [ - { title: "Coordinator API", url: "ace/reference/api/coordinator" }, - { title: "Evaluation API", url: "ace/reference/api/evaluation" }, - { title: "Reporting API", url: "ace/reference/api/reporting" }, + { + title: "Coordinator API", + url: "api/ace/coordinator/docs", + openInNewTab: true, + }, + { + title: "Evaluation API (MVP)", + url: "api/ace/evaluation/docs", + openInNewTab: true, + }, + { + title: "Reporting API", + url: "api/ace/reporting/docs", + openInNewTab: true, + }, ], }, ], diff --git a/src/content/ace/beta-scope.mdx b/src/content/ace/beta-scope.mdx index dcdb7c354a2..6cc2fbc0729 100644 --- a/src/content/ace/beta-scope.mdx +++ b/src/content/ace/beta-scope.mdx @@ -4,16 +4,14 @@ title: "Beta Scope" date: Last Modified metadata: description: "ACE Beta scope: supported networks, managed offchain risk policies, contract types, customizations, signing model, and contract upgradeability." - datePublished: "2026-04-20" - lastModified: "2026-08-18" + datePublished: "2026-03-31" + lastModified: "2026-07-17" --- import { Aside } from "@components" +import AceCallout from "@features/ace/callouts/AceCallout.astro" - + ACE Beta is an early-access release for testing and integration on supported mainnet and testnet networks. The limitations listed below are all areas of active development. Each will be addressed as ACE progresses toward general availability. @@ -23,10 +21,10 @@ ACE Beta is available on selected [mainnet and testnet networks](/ace/supported- ## No custom extractors or mappers -ACE Beta provides a library of [pre-built, audited policies](/ace/reference/policy-library) (allowlists, volume limits, role-based access control, pause controls, and more). You can also register your own [custom policies](/ace/concepts/policy-management). The following customizations are **not available** through the platform during Beta: +ACE Beta provides a library of [pre-built, audited policies](/ace/reference/policy-library) (allowlists, volume limits, role-based access control, pause controls, and more). You can also register your own [custom policies](/ace/guides/policy-manager/custom-policies). The following customizations are **not available** through the platform during Beta: -- **Custom extractors**: Writing extractors for function signatures beyond the pre-built ERC-20 and ERC-3643 set. -- **Custom mappers**: Deploying mapper contracts that transform or combine extracted parameters before they reach a policy. +- **Custom extractors** — Writing extractors for function signatures beyond the pre-built ERC-20 and ERC-3643 set. +- **Custom mappers** — Deploying mapper contracts that transform or combine extracted parameters before they reach a policy. Because custom extractors are not available, the platform supports **ERC-20 and ERC-3643** function signatures only. These are the only contract types that get the full managed experience — policy configuration, reporting, and monitoring — through the Platform UI and Coordinator API. If you are building a different type of contract (vault, DEX, lending protocol), making it ACE-compatible could require custom extractors. Support for additional contract types and custom extractors is on the roadmap. @@ -46,16 +44,31 @@ In addition to attestation-based checks (verifying whether a credential exists), Data Validators are a **curated catalog maintained by Chainlink**, not something organizations deploy themselves. This is **by design**: curating the available validators and their data schemas ensures no personally identifiable information (PII) are used. The first available validator supports **jurisdiction control** using [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes; Chainlink may add further validators over time. Bringing your own Data Validator is not offered — this is a permanent design choice, not a Beta limitation. -For the conceptual explanation of attestation-only vs. Credential Data Validator checks, see [Cross-Chain Identity — Credential data and privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy). +To get started, see [Managing Data Validators](/ace/guides/policy-manager/manage-data-validators). For the conceptual explanation of attestation-only vs. Credential Data Validator checks, see [Cross-Chain Identity — Credential data and privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy). ## Signing model is chosen at onboarding ACE supports two signing models: **delegated signing** (Chainlink signs and executes transactions on your behalf) and **self-signing** (you sign operations yourself using the [CRE Connect SDK](https://github.com/smartcontractkit/crec-sdk)). Your organization chooses its signing model during onboarding. -In both models, you retain full ownership of your contracts through the [CRE Connect Wallet](/ace/concepts/key-terms#cre-connect-wallet). See [Signing & Ownership Model](/ace/concepts/signing-ownership) for details on how each model works. +In both models, you retain full ownership of your contracts through the [CRE Connect Wallet](/ace/concepts/signing-ownership). See [Signing & Ownership Model](/ace/concepts/signing-ownership) for details on how each model works. ## Managed offchain risk policies are limited during Beta -ACE Beta provides a managed policy that screens transaction participants with TRM Wallet Screening and delivers approved permits onchain through a managed CRE workflow. +ACE Beta provides a managed `wallet_risk_scoring` policy that screens transaction participants with TRM Wallet Screening and delivers approved permits onchain through a managed CRE workflow. + + + +The following limitations apply: + +- **TRM access required** — Your organization must have a TRM Labs account with Wallet Screening API access and provide its own API credential through CRE Vault DON. +- **One policy type** — `wallet_risk_scoring` is the only managed offchain policy available. Custom offchain integrations require assistance from Chainlink. +- **One active policy per organization** — Archive the existing offchain policy before creating another. +- **Ten addresses per evaluation** — A workflow execution can screen at most ten unique wallet addresses. +- **Fixed permit lifetime and usage** — Managed permits are single-use and do not expire. These values are not configurable in the current release. +- **Extractor-dependent protection** — Permit parameters must correspond to supported extractor outputs and exactly match the values extracted from the eventual onchain transaction. +- **CRE quotas apply** — Evaluations are subject to current [CRE Service Quotas](https://docs.chain.link/cre/service-quotas), including the HTTP trigger rate limit. -See [Off-Chain Policy Execution](/ace/concepts/off-chain-policies) for a conceptual overview of managed and custom offchain policies. +See [Offchain Policies](/ace/guides/policy-manager/offchain-policies) for an overview, [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) to configure wallet screening, and [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) to integrate evaluations into an application. diff --git a/src/content/ace/concepts/architecture.mdx b/src/content/ace/concepts/architecture.mdx index 95fb577e212..d61e9976ed7 100644 --- a/src/content/ace/concepts/architecture.mdx +++ b/src/content/ace/concepts/architecture.mdx @@ -4,11 +4,14 @@ title: "ACE Architecture" date: Last Modified metadata: description: "Understand the Chainlink ACE system architecture: onchain contracts, the ACE Platform, and how the Policy Manager, Identity Manager, Reporting Manager, and Chainlink infrastructure work together to power automated compliance." - datePublished: "2026-04-20" - lastModified: "2026-04-20" + datePublished: "2026-03-31" + lastModified: "2026-03-31" --- import { Aside, ClickToZoom } from "@components" +import AceCallout from "@features/ace/callouts/AceCallout.astro" + + ACE has two layers: **onchain smart contracts** that enforce compliance rules on the blockchain, and the **[ACE Platform](/ace/concepts/key-terms#ace-platform)** (UI and APIs) that lets you manage them. Under the hood, Chainlink infrastructure connects the two — routing your platform actions to the blockchain and indexing onchain events back into the Reporting API. This page gives a bird's-eye view of how all the pieces fit together. @@ -23,7 +26,7 @@ The following diagram shows the complete ACE architecture, from the ACE Platform The **ACE Platform** is everything you interact with: the **Platform UI**, the **Coordinator API** (to manage ACE resources), and the **Reporting API** (to query what happened onchain). The UI calls the Coordinator API under the hood, so both paths converge. -When you manage ACE (create policies, register identities, etc.), the Coordinator API routes your request through **CRE Connect**, which executes the blockchain transaction via your organization's **CRE Connect Wallet**. The CRE Connect Wallet owns all your ACE contracts and verifies that only authorized operators can act on them. +When you manage ACE (create policies, register identities, etc.), the Coordinator API routes your request through **CRE Connect**, which executes the blockchain transaction via your organization's **[CRE Connect Wallet](/ace/concepts/signing-ownership)**. The CRE Connect Wallet owns all your ACE contracts and verifies that only authorized operators can act on them. In the other direction, when policies run onchain, the contracts emit events. **Chainlink's indexing infrastructure** continuously monitors these events, indexes the data, and makes it available through the **Reporting API** — giving you a queryable view of all policy run activity, transaction history, and onchain state. @@ -37,19 +40,19 @@ ACE Beta provides three managers that abstract away the complexity of managing o The Policy Manager lets you create, configure, and deploy onchain compliance rules for your smart contracts. You can browse available policy types (allowlist, volume limits, role-based access control, etc.), create policy instances with per-network configuration, and attach them to specific function selectors on your protected contracts. -The Policy Manager operates on the **Policy Management** onchain contracts: it deploys and configures PolicyEngine instances, policy contracts, and extractors on your behalf. See the [Coordinator API](/ace/reference/api/coordinator) to get started. +The Policy Manager operates on the **Policy Management** onchain contracts: it deploys and configures PolicyEngine instances, policy contracts, and extractors on your behalf. See the [Policy Manager guides](/ace/guides/policy-manager/manage-engines) or the [Coordinator API reference](/api/ace/coordinator/docs) to get started. ### Identity Manager The Identity Manager lets you manage cross-chain identities and credentials. You can create identity and credential registries, register wallet addresses to CCIDs, define credential types, and issue credentials to users. -The Identity Manager operates on the **Cross-Chain Identity** onchain contracts: it writes to IdentityRegistry and CredentialRegistry instances on your behalf. See the [Coordinator API](/ace/reference/api/coordinator) to get started. +The Identity Manager operates on the **Cross-Chain Identity** onchain contracts: it writes to IdentityRegistry and CredentialRegistry instances on your behalf. See the [Identity Manager guides](/ace/guides/identity-manager/manage-identities) or the [Coordinator API reference](/api/ace/coordinator/docs) to get started. ### Reporting Manager The Reporting Manager gives you read-only access to onchain state and transaction history. You can query policy engines and their configurations, look up identities and credentials, and view policy run transactions with filtering by network, target contract, and time range. -The Reporting Manager exposes data through the **Reporting API**. Under the hood, Chainlink's indexing infrastructure monitors onchain events (such as `PolicyRunComplete`) emitted by your PolicyEngines and indexes the data so it can be queried through the API. See the [Reporting API overview](/ace/reference/api/reporting) for details. +The Reporting Manager exposes data through the **Reporting API**. Under the hood, Chainlink's indexing infrastructure monitors onchain events (such as `PolicyRunComplete`) emitted by your PolicyEngines and indexes the data so it can be queried through the API. See the [API Overview](/ace/reference/apis) for available endpoints. -To view or change the default result for a target contract, use the ACE Platform UI or Coordinator API. +To view or change the default result for a target contract, see [Managing Targets — Default allow behavior](/ace/guides/policy-manager/manage-targets#default-allow-behavior). ## Managing the policy chain @@ -95,4 +95,4 @@ Each policy in the chain costs gas. While the PolicyEngine is designed for effic Any time you add, remove, or reorder a policy, review the full chain to confirm the new ordering matches your intent. A single misplaced policy can create a gap in your compliance coverage. -For additional security guidance around trust boundaries, external call risks, and context handling, consult your integration documentation. +For additional security guidance around trust boundaries, external call risks, and context handling, see [Security Considerations](/ace/guides/policy-manager/contracts/security-considerations). diff --git a/src/content/ace/concepts/reporting.mdx b/src/content/ace/concepts/reporting.mdx index e17a2db349d..b97c6873368 100644 --- a/src/content/ace/concepts/reporting.mdx +++ b/src/content/ace/concepts/reporting.mdx @@ -4,8 +4,8 @@ title: "Reporting Manager" date: Last Modified metadata: description: "Understand the ACE Reporting Manager: query policy run transactions, compliance configurations, identities, and credentials with point-in-time historical snapshots for audit and regulatory evidence." - datePublished: "2026-04-20" - lastModified: "2026-04-20" + datePublished: "2026-03-31" + lastModified: "2026-03-31" --- import { Aside } from "@components" @@ -64,21 +64,23 @@ This gives you a complete snapshot of "what compliance rules protect this contra ### Identities -Look up identity records by wallet address, identity registry, credential type, credential registry, or CCID. Each identity includes all registry memberships (which registries, which chains, which wallet addresses are mapped). You can optionally request full credential details, including the credential type identifier, the issuing credential registry, issuance and expiration timestamps, credential data, and full on-chain provenance (block number, transaction hash). +Look up identity records by wallet address, identity registry, credential type, credential registry, or CCID. Each identity includes all registry memberships (which registries, which chains, which wallet addresses are mapped). + +Set `include_credential_details=true` to include each identity's credentials in the response. Each credential record contains the credential type identifier, the issuing credential registry, issuance and expiration timestamps, credential data, and full on-chain provenance (block number, transaction hash). This lets you answer questions like "does this wallet address belong to a registered identity?" or "which identities have a KYC credential issued by this credential registry?" ## Point-in-time queries -Policies, Identities, and Targets all support point-in-time historical queries. This lets you reconstruct the state of your compliance system at any historical moment: +The Policies, Identities, and Targets endpoints each accept a required **`as_of`** timestamp parameter. This lets you reconstruct the state of your compliance system at any historical moment: - **What policies were active** on a contract on a specific date? - **What credentials** did an identity hold at the time of a transaction? - **What protections** were configured on a target contract last quarter? -Point-in-time queries are critical for regulatory investigations where you need to prove not just that compliance rules exist _today_, but that they were in place _when a specific event occurred_. The API returns the version of each resource that was effective at the specified time, including resources that have since been updated or removed. +Point-in-time queries are critical for regulatory investigations where you need to prove not just that compliance rules exist _today_, but that they were in place _when a specific event occurred_. The `as_of` parameter returns the version of each resource that was effective at the specified time, including resources that have since been updated or removed. -Transactions support time range filtering instead, since transactions are discrete events rather than stateful resources. +The Transactions endpoint uses `from` and `to` time range filters instead, since transactions are discrete events rather than stateful resources. ## How data flows @@ -103,9 +105,9 @@ This happens automatically for all contracts deployed through the ACE Platform. During Beta, the Reporting Manager is **API-only** — there is no reporting UI. You interact with it exclusively through the Reporting API. -The API provides read-only access. All compliance configuration changes (deploying policies, registering identities, issuing credentials) are done through the [Coordinator API](/ace/reference/api/coordinator) or Platform UI. +The API provides read-only access. All compliance configuration changes (deploying policies, registering identities, issuing credentials) are done through the Coordinator API or Platform UI. ## Next steps -- **[Reporting API overview](/ace/reference/api/reporting)** — What the Reporting API provides and what resources can be queried. +- **[Interactive API Reference](/api/ace/reporting/docs)** — Try API calls directly in the browser with full request/response schemas. - **[Architecture](/ace/concepts/architecture#how-ace-observes-onchain-activity-read-path)** — How the indexing pipeline connects onchain events to the Reporting API. diff --git a/src/content/ace/concepts/security.mdx b/src/content/ace/concepts/security.mdx new file mode 100644 index 00000000000..bdfb212848f --- /dev/null +++ b/src/content/ace/concepts/security.mdx @@ -0,0 +1,51 @@ +--- +section: ace +title: "Security Model" +date: Last Modified +metadata: + description: "Learn about the Chainlink ACE security model: policy administration controls, execution ordering, registry governance, and privacy guarantees." + datePublished: "2026-03-31" + lastModified: "2026-03-31" +--- + +This page covers the governance and operational security principles behind how ACE protects onchain assets and identity data — the controls, ordering guarantees, and privacy properties that administrators, auditors, and compliance teams should understand. + +For implementation-level security guidance (trust boundaries for policies and extractors, context handling, view function requirements), see [Security Considerations for Smart Contracts](/ace/guides/policy-manager/contracts/security-considerations). + +## Policy administration is a critical control + +The ability to add, remove, or reorder policies in a PolicyEngine is the most sensitive administrative power in ACE. An actor who gains control over these functions can effectively disable or bypass all compliance rules for every contract connected to that engine. + +In ACE Beta, policy administration is handled through the [ACE Platform](/ace/concepts/key-terms#ace-platform) — the Coordinator API and Platform UI — which means configuration changes go through the platform's authentication and authorization layer. The underlying onchain contracts enforce that only the [CRE Connect Wallet](/ace/concepts/key-terms#cre-connect-wallet) (which the platform operates on your behalf) can call administrative functions like `addPolicy`, `removePolicy`, `setExtractor`, and `setDefaultAllow`. + +## Policy execution order matters + +Policies execute in a strict, sequential order — the order they were added to the PolicyEngine for a given function selector. This order has direct security implications because of how the three policy outcomes interact: + +- **Reject** halts execution immediately and reverts the transaction. No subsequent policies run. +- **Allow** approves the transaction immediately and **bypasses all subsequent policies**. +- **Continue** passes the decision to the next policy in the chain. + +Because Allow skips everything after it, a permissive policy placed too early in the chain can inadvertently bypass critical security checks. For example, if an admin bypass policy is placed before a sanctions check, an admin address would never be screened. + +**Best practice:** Order restrictive policies (sanctions screening, denylist checks) before permissive ones (admin bypass, authorized sender lists). This ensures that hard blocks cannot be circumvented by an early Allow. + +For a detailed guide on ordering strategies, see [Policy Ordering & Composition](/ace/concepts/policy-ordering). + +## Registry governance + +The IdentityRegistry and CredentialRegistry are not protected by simple access-control lists. Instead, their administrative functions — `registerIdentity`, `registerCredential`, `removeCredential`, and others — are themselves protected by a PolicyEngine. This means: + +- Only addresses authorized by the registry's PolicyEngine can modify identity or credential data. +- The same policy model that protects application contracts also protects the identity infrastructure. +- Authorization can be as simple as an allowlist of Credential Issuers, or as sophisticated as a multi-policy chain with role checks and volume limits on issuance. + +This design ensures that credential issuance is governed by explicit, auditable rules rather than hardcoded access controls. In ACE Beta, the [Identity Manager](/ace/concepts/key-terms#identity-manager) handles registry governance through the platform — users in your organization can create registries, register identities, define credential types, and issue credentials, and the platform's CRE Connect Wallet executes these operations onchain. + +## Privacy guarantees + +ACE is designed so that no personally identifiable information (PII) is stored onchain: + +- **Credential data** is arbitrary `bytes` — typically a hash of offchain data or a non-sensitive reference. The system never requires raw PII to be written to the blockchain. +- **CCID-to-address mappings** are publicly readable onchain. This is by design — it enables cross-chain verification — but it means anyone can see which addresses share the same identity. For applications where this transparency is a concern, multiple CCIDs per user can be used to limit correlation across domains. See [Cross-Chain Identity: Privacy and correlation](/ace/concepts/cross-chain-identity#privacy-and-correlation) for details. +- **Credential type identifiers** are hashed (`keccak256`) but use known namespaced strings (e.g., `common.kyc`), so standard types are effectively public knowledge. diff --git a/src/content/ace/concepts/signing-ownership.mdx b/src/content/ace/concepts/signing-ownership.mdx index d06466ed1f4..449da426264 100644 --- a/src/content/ace/concepts/signing-ownership.mdx +++ b/src/content/ace/concepts/signing-ownership.mdx @@ -4,8 +4,8 @@ title: "Signing and Ownership Model" date: Last Modified metadata: description: "Understand how Chainlink ACE handles transaction signing and contract ownership: delegated signing and self-signing models, CRE Connect Wallet, and how to choose." - datePublished: "2026-07-17" - lastModified: "2026-08-18" + datePublished: "2026-03-31" + lastModified: "2026-07-17" --- import { Aside, ClickToZoom } from "@components" @@ -29,22 +29,68 @@ In the delegated model, ACE uses a **delegated trust** approach centered around ### How it works -When you trigger an action — whether from the ACE Platform UI or the API: +When your organization onboards onto ACE, the system deploys a CRE Connect Wallet for you on every network you require. + +Here is the key principle: + +- **Your wallet owns the CRE Connect Wallet.** +- **The CRE Connect Wallet owns all your ACE contracts** (policy engines, registries, policies, etc.). +- **Chainlink is registered as an authorized operator** on your CRE Connect Wallet — allowed to execute operations, but nothing more. + +This means you indirectly own every contract that ACE deploys for your organization, through the CRE Connect Wallet. + +### Permissions + +The CRE Connect Wallet enforces strict permission boundaries between you and Chainlink: + +| Action | You (client) | Chainlink | +| :--------------------------------------------------------- | :----------- | :---------- | +| **Change ownership** of the CRE Connect Wallet | Allowed | Not allowed | +| **Manage authorized signers** (add/remove who can operate) | Allowed | Not allowed | +| **Execute operations** on your contracts | Allowed | Allowed | + +Chainlink can only execute operations (deploy contracts, configure policies, register identities, etc.) — it cannot change who owns the account or who is authorized to sign. + +### What happens during setup + +When your organization is onboarded onto ACE: -1. The **Coordinator API** receives your request and sends it to **CRE Connect**. -1. CRE Connect prepares and signs the blockchain transaction on your behalf. -1. The signed transaction is routed through your organization's **CRE Connect Wallet** onchain. -1. The CRE Connect Wallet verifies authorization and executes the operation on the target contract. +1. You provide your wallet address to the ACE platform. +1. Chainlink creates an internal signing key dedicated to your organization. This key is managed entirely by Chainlink — you never see or interact with it. +1. ACE deploys a CRE Connect Wallet onchain, sets your wallet as its owner, and registers Chainlink's signing key as an authorized operator — all in a single deployment step. +1. ACE deploys all application contracts (policy engines, registries, etc.) and assigns ownership to your CRE Connect Wallet. -You never handle signing keys or submit raw transactions. Chainlink handles the entire signing and execution pipeline, while you retain full ownership of all deployed contracts. +Once setup is complete, the permission boundaries described above take effect: only you can change ownership or manage authorized signers. Chainlink can only execute operations. + + + +### What happens during operations + +When you trigger an action — whether from the ACE platform UI or the API: + +1. You perform an action (e.g., "deploy a new policy instance on Ethereum"). +1. ACE prepares the corresponding blockchain transaction and signs it using Chainlink's internal signing key for your organization. +1. ACE sends the signed transaction to your CRE Connect Wallet. +1. The CRE Connect Wallet verifies that the signer is in its list of authorized operators. +1. If authorized, the CRE Connect Wallet executes the operation on the target contract. + +During day-to-day operations, you never interact with a blockchain wallet or sign a transaction. ACE handles the signing and execution, while your CRE Connect Wallet enforces that only authorized operators can act. + + ### Your safety net You are always in control. Because your wallet owns the CRE Connect Wallet, you can at any time: -- Interact with the CRE Connect Wallet directly, bypassing Chainlink. -- Add or remove authorized operators (including Chainlink). -- Take over operational responsibility entirely. +- **Interact with the CRE Connect Wallet directly** — bypassing ACE entirely. +- **Remove Chainlink as an authorized signer** — immediately revoking ACE's ability to execute operations on your contracts. +- **Add other signers** — granting operation rights to your own keys or third parties. Revoking Chainlink's access does not affect your contract ownership. Your contracts remain yours, managed through your CRE Connect Wallet. You would simply take over operational responsibility. @@ -62,14 +108,14 @@ When you trigger an action — whether from the ACE Platform UI or the API: 1. You perform an action (e.g., "deploy a new policy instance on Ethereum"). 1. ACE prepares the corresponding blockchain transaction and creates an **unsigned draft operation** with status `pending_signature`. -1. You poll for pending operations using the CRE Connect SDK and retrieve the draft. +1. You poll for pending operations using the [CRE Connect SDK](https://github.com/smartcontractkit/crec-sdk) and retrieve the draft. 1. You sign the operation using your own key (EIP-712 typed data signing). 1. You submit the signed operation back through the CRE Connect SDK. 1. The CRE Connect Wallet verifies your signature and executes the operation onchain. ### Polling and signing with the CRE Connect SDK -The [CRE Connect SDK](https://github.com/smartcontractkit/crec-sdk) is a client library that provides all the tools needed for the self-signing workflow: +The [CRE Connect SDK](https://github.com/smartcontractkit/crec-sdk) is a Go client library that provides all the tools needed for the self-signing workflow: - **List pending operations** — Poll for unsigned draft operations waiting for your signature. - **Hash operations** — Compute the EIP-712 digest for an operation locally. @@ -81,9 +127,3 @@ The SDK supports multiple signer backends: local ECDSA keys, AWS KMS, HashiCorp ### Your safety net The same safety net applies as with delegated signing. Because your wallet owns the CRE Connect Wallet, you can at any time interact with it directly, add or remove authorized signers, or take over operational responsibility entirely. - -## Related pages - -- [Architecture](/ace/concepts/architecture) — how signing fits into the overall ACE system -- [Key Terms](/ace/concepts/key-terms) — definitions of CRE Connect, CRE Connect Wallet, and related terms -- [Beta Scope](/ace/beta-scope) — current scope and limitations diff --git a/src/content/ace/getting-started/account-setup.mdx b/src/content/ace/getting-started/account-setup.mdx new file mode 100644 index 00000000000..6b242d4ea9d --- /dev/null +++ b/src/content/ace/getting-started/account-setup.mdx @@ -0,0 +1,169 @@ +--- +section: ace +title: "Account Setup" +date: Last Modified +metadata: + description: "Set up your Chainlink ACE account: create an organization on app.chain.link, get your API key, create CRE Connect Wallets, and prepare for your first ACE deployment." + datePublished: "2026-03-31" + lastModified: "2026-04-15" +--- + +import { Aside, ClickToZoom, MethodTabs } from "@components" +import AceCallout from "@features/ace/callouts/AceCallout.astro" + + + +This page walks through the shared setup steps for all ACE users. Whether you use the [Policy Manager](/ace/getting-started/policy-manager) to enforce compliance on smart contracts or the [Identity Manager](/ace/getting-started/identity-manager) to manage cross-chain identities and credentials, complete these steps first. + +## 1. Create your organization + +Go to [app.chain.link](https://app.chain.link) and create an account or sign in. + +Once signed in, click **"My Org"** in the bottom-left corner of the sidebar to open the Organization page. Your **Organization ID** is displayed in the page header — copy it. + + + +## 2. Share your Organization ID + +Share your Organization ID with your Chainlink contact so they can enable the ACE service for your organization. You cannot create API keys or use ACE until this step is complete. + + + +## 3. Create an API key + + + +Once your account has been provisioned for the ACE service, create an API key for authentication: + +1. Log in to the [Chainlink App](https://app.chain.link), click **"My Org"** at the bottom of the left sidebar, then select the **"APIs"** tab. +1. Click **"+ Organization API"**. + +1. Enter a name for the key and select an expiration period (1 day, 1 month, or 1 year). + +1. Click **"Generate"**. The API key is displayed once — copy it immediately. + + + +You will use this key in the `Authorization` header for all [Coordinator API](/api/ace/coordinator/docs) and [Reporting API](/api/ace/reporting/docs) calls: + +```bash +curl https://ace.api.chain.link/v1/ \ + -H "Authorization: Apikey " +``` + +## 4. Set up CRE Connect Wallets + +Before you can use ACE — whether from the Platform UI or the Coordinator API — you need a **CRE Connect Wallet** on each blockchain network where you want to operate. + +A CRE Connect Wallet is a dedicated onchain smart contract wallet that acts as the execution gateway for all ACE actions on a given chain. When you trigger an action (deploy a policy engine, register an identity, issue a credential), the ACE platform executes the blockchain transaction through your CRE Connect Wallet. For a full explanation of how the signing and ownership model works, see [Signing & Ownership Model](/ace/concepts/signing-ownership). + +Key points: + +- **One wallet per chain.** You need a CRE Connect Wallet on every network where you plan to use ACE. See [Supported Networks](/ace/supported-networks) for available chains. +- **You own it.** The `owner_address` you provide when creating the wallet becomes the owner of the CRE Connect Wallet onchain. +- **Chainlink operates through it.** Chainlink is registered as an authorized operator — allowed to execute operations on your behalf, but unable to change ownership or authorization settings. +- **Self-signing organizations** must also provide the `address` of the ECDSA signer authorized to sign operations. See [Signing & Ownership Model](/ace/concepts/signing-ownership) for details on how each signing model works. + + + + 1. In the [Chainlink App](https://app.chain.link), open the **Compliance** section from the left sidebar and click **Home**. + 1. Click **View Settings**, then click **Enable new network**. + 1. Select the network you want to enable and provide an EOA address as the wallet owner. To deploy wallets on multiple chains at once, click **Add more** to add additional networks before submitting. + + 1. The platform deploys your CRE Connect Wallet onchain. Once created, the wallet appears in your settings with its onchain address and deployment status. + + + The Coordinator API identifies networks using **chain selectors** — unique numeric identifiers used by Chainlink infrastructure to reference specific blockchains. These are different from standard chain IDs. You can find the chain selector for each supported network on the [Supported Networks](/ace/supported-networks) page. + + Create wallets on one or more networks in a single request. The `owner_address` is the EOA that becomes the onchain owner of the CRE Connect Wallet. This example creates wallets on Ethereum Sepolia and Arbitrum Sepolia: + + ```bash + curl -X POST https://ace.api.chain.link/v1/wallets \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "wallets": [ + { + "owner_address": "", + "chain_selector": "16015286601757825753" + }, + { + "owner_address": "", + "chain_selector": "3478487238524512106" + } + ] + }' + ``` + + The response includes the deployed wallet address and status for each chain: + + ```json + { + "wallets": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "org_id": "org-123", + "chain_selector": "16015286601757825753", + "owner_address": "0x1234...5678", + "wallet_address": "0xabcd...ef01", + "status": "created", + "created_at": 1775580000, + "updated_at": 1775580000 + }, + { + "id": "8b2e1c4a-9f3d-4a7b-b6e8-1d2f3a4b5c6d", + "org_id": "org-123", + "chain_selector": "3478487238524512106", + "owner_address": "0x1234...5678", + "wallet_address": "0x9876...5432", + "status": "created", + "created_at": 1775580000, + "updated_at": 1775580000 + } + ] + } + ``` + + Verify your wallets at any time: + + ```bash + curl https://ace.api.chain.link/v1/wallets \ + -H "Authorization: Apikey " + ``` + + For the full API schema and parameters, see the Coordinator API Reference. + + + + +## What happens next + +Your next step depends on what you need to do: + +| If you need to... | Next step | +| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| Enforce compliance policies on smart contracts | Deploy a PolicyEngine and integrate your contract. Continue to the [Policy Manager Quick Start](/ace/getting-started/policy-manager). | +| Manage cross-chain identities and issue credentials | Set up identity and credential registries. Continue to the [Identity Manager Quick Start](/ace/getting-started/identity-manager). | + +Many organizations do both. Start with whichever is most relevant to your first use case — the quick start guides cross-reference each other where the workflows intersect. diff --git a/src/content/ace/getting-started/identity-manager.mdx b/src/content/ace/getting-started/identity-manager.mdx new file mode 100644 index 00000000000..20af92c052e --- /dev/null +++ b/src/content/ace/getting-started/identity-manager.mdx @@ -0,0 +1,191 @@ +--- +section: ace +title: "Identity Manager Quick Start" +date: Last Modified +metadata: + description: "Get started as an Identity Manager with Chainlink ACE: create your account, set up identity and credential registries, register identities, issue credentials, and verify compliance via the Reporting Manager." + datePublished: "2026-04-06" + lastModified: "2026-05-26" +--- + +import { Aside, ClickToZoom, MethodTabs } from "@components" +import AceCallout from "@features/ace/callouts/AceCallout.astro" + + + +This guide walks you through the **Identity Manager** — the ACE component for managing identity registries, registering cross-chain identities (CCIDs), and issuing credentials such as Proof of Identity, accreditation proofs, or sanctions clearance. By the end you will have identities registered and credentials issued on a supported network. + +### 1. Prerequisites + +- Familiarity with [Cross-Chain Identity](/ace/concepts/cross-chain-identity) concepts — CCIDs, credential registries, credential types, and credential sources +- **Must-read before proceeding:** [Signing & Ownership Model](/ace/concepts/signing-ownership) — understand how ACE manages keys, who owns what, and how the delegated trust model works + +### 2. Account setup + +Complete the [Account Setup](/ace/getting-started/account-setup) steps — organization creation, API key generation, and CRE Connect Wallet deployment — before proceeding. + +### 3. Set up your registries + +A **registry** is the top-level resource that groups an **identity registry** and a **credential registry**, deployed together on each chain you operate on. + +- The identity registry maps wallet addresses to cross-chain identities (CCIDs) +- The credential registry stores credential attestations linked to those CCIDs + +See [Managing Registries](/ace/guides/identity-manager/manage-registries) for full details. + + + + 1. In the [Chainlink App](https://app.chain.link), navigate to **Compliance > Identity Manager** in the left sidebar. + 1. Click the **Add a new registry** card. + 1. Enter a **Registry name** and optional **Description**, then click **Continue**. + 1. Select the networks you want to deploy the registry to, then click **Deploy registry**. ACE deploys the identity and credential registry contracts on each selected chain. + 1. Once deployed, click the **gear icon** next to the registry name to view the on-chain contract addresses for each identity and credential registry. + + + + Create a registry with a `POST` request: + + ```bash + curl -X POST "https://ace.api.chain.link/v1/registries" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "name": "My First Registry", + "description": "Identity and credential registries for my organization", + "identity_registries": [ + { "name": "Identity Registry - Sepolia", "description": "Identity registry on Ethereum Sepolia", "chain_selector": "16015286601757825753" }, + { "name": "Identity Registry - Arbitrum", "description": "Identity registry on Arbitrum Sepolia", "chain_selector": "3478487238524512106" } + ], + "credential_registries": [ + { "name": "Credential Registry - Sepolia", "description": "Credential registry on Ethereum Sepolia", "chain_selector": "16015286601757825753" }, + { "name": "Credential Registry - Arbitrum", "description": "Credential registry on Arbitrum Sepolia", "chain_selector": "3478487238524512106" } + ] + }' + ``` + + ACE deploys the identity and credential registry contracts on each specified chain. The response includes the registry `id` you will use in subsequent steps. + + + + +### 4. Define credential types + +Credential types represent the categories of attestation you issue — for example, Proof of Identity, accredited investor, or sanctions clearance. Each credential type is scoped to a specific registry and identified by a `credential_type` string that gets hashed on-chain to a `credential_type_hash`. This value is hashed using keccak256 — the standard cryptographic hash function used by Ethereum and EVM-compatible blockchains — and the resulting `credential_type_hash` is what gets recorded on-chain and referenced by policies. + + + + 1. In the [Chainlink App](https://app.chain.link), navigate to **Compliance > Identity Manager** and click on your registry card. + 1. Select the **Credential types** tab, then click **+ Add credential type**. + 1. In the drawer that opens, fill in: + - **Internal Display Name** — A label used by your team to identify and track this credential (not stored on-chain). + - **Credential** — The credential type string (e.g., `proof_of_identity_basic`). + - **Description** — Optional description. + 1. Click **Save**. + + + Create a credential type with a `POST` request. The `registry_id` was returned in the response when you created your registry. To retrieve it later, list your registries with `GET /registries`. + + ```bash + curl -X POST "https://ace.api.chain.link/v1/credential-types" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "registry_id": "", + "title": "KYC Verification", + "credential_type": "kyc_basic", + "description": "Basic KYC identity verification" + }' + ``` + + You can list existing credential types for a registry at any time: + + ```bash + curl -X GET "https://ace.api.chain.link/v1/credential-types?registry_id=a1b2c3d4-5678-9abc-def0-1234567890ab" \ + -H "Authorization: Apikey " + ``` + + + + +### 5. Register identities and issue credentials + +A cross-chain identity (CCID) aggregates one or more wallet addresses across EVM chains into a single logical entity. When you register an identity, you provide the on-chain addresses that belong to that entity and ACE writes the mapping into the identity registry on each relevant chain. + +A single CCID can span multiple chains and addresses — for example, one entity might have wallets on Ethereum, Arbitrum, and Avalanche that all resolve to the same CCID. + +Credentials are attestations linked to a registered identity. During Beta, ACE uses an **attestation-only** model — the Identity Manager asserts that a credential holds for a given CCID, and the credential registry records that attestation on-chain. + + + + The Platform UI lets you register an identity and assign credentials in a single flow: + + 1. In the [Chainlink App](https://app.chain.link), navigate to **Compliance > Identity Manager** and click on your registry card. + 1. Click **+ Add identity**. + 1. Fill in: + - **Alias** — An internal name for this identity. This is never written on-chain. + - **Metadata** — Optional internal reference data, also never written on-chain. + 1. Click **Continue**. + 1. Enter the wallet address(es) for the identity on each network where your registry is deployed. Click **Add more** to add multiple addresses per network or addresses on different networks. + 1. Click **Continue**. + 1. Assign credential types to this identity and set an expiration date for each credential. The expiration is optional — if omitted, the credential does not expire. + 1. Click **Deploy identity**. ACE registers the identity and issues the assigned credentials on-chain in one transaction. + + + + Register an identity and issue credentials in a single `POST` request by including the optional `credentials` array: + + ```bash + curl -X POST "https://ace.api.chain.link/v1/identities" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "title": "My First Identity", + "entity_id": "user-12345", + "registry_id": "", + "description": "Test identity for getting started", + "onchain_identities": [ + { + "address": "0x1234567890abcdef1234567890abcdef12345678", + "chain_selector": "16015286601757825753" + } + ], + "credentials": [ + { + "credential_type_id": "", + "expires_at": 1800000000 + } + ] + }' + ``` + + The `credentials` array is optional — omit it to register an identity without credentials. Within each credential, `expires_at` is also optional (a Unix timestamp); if omitted, the credential does not expire. + + You can also issue credentials separately after the identity is created using `POST /credentials`. See [Managing Credentials](/ace/guides/identity-manager/manage-credentials) for details. + + + + + + +### 6. Verify via Reporting + +After issuing credentials, confirm they are visible and queryable through the **Reporting Manager**. The Reporting Manager provides a read-only view of all identities and credentials across your registries, which Policy Managers rely on when evaluating identity-based policies at transaction time. + +- Open the Reporting API to query credentials by identity, entity, or registry +- See [Reporting](/ace/concepts/reporting) for details on how reporting data flows into policy evaluation + +Once credentials appear in reporting, Policy Managers can reference them in identity-based policies such as the [Credential Registry Identity Validator](/ace/reference/policy-library/credential-registry-identity-validator-policy). + +### 7. What's next + +Explore the detailed guides for each Identity Manager workflow: + +- [Managing Identities](/ace/guides/identity-manager/manage-identities) — add, update, and remove CCIDs and their on-chain address mappings +- [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types) — create and organize the credential categories your registry supports +- [Managing Credentials](/ace/guides/identity-manager/manage-credentials) — issue, revoke, and set expiration on credentials +- [Managing Registries](/ace/guides/identity-manager/manage-registries) — view and manage your identity and credential registry deployments diff --git a/src/content/ace/getting-started/index.mdx b/src/content/ace/getting-started/index.mdx index aeb0e862606..ba95fd743de 100644 --- a/src/content/ace/getting-started/index.mdx +++ b/src/content/ace/getting-started/index.mdx @@ -2,19 +2,43 @@ section: ace title: "Getting Started with ACE" isIndex: true +date: Last Modified metadata: - description: "ACE (Automated Compliance Engine) is currently in private beta. Contact Chainlink to request access." - datePublished: "2026-04-20" + description: "Choose your onboarding path for Chainlink ACE: Policy Manager (enforce compliance on smart contracts), Identity Manager (manage cross-chain identities and credentials), or Reporting Manager (query compliance data)." + datePublished: "2026-03-31" + lastModified: "2026-04-15" --- -## Get Access to ACE +import { Aside } from "@components" +import AceCallout from "@features/ace/callouts/AceCallout.astro" -Chainlink ACE is currently in **private beta**. To request access, schedule a demo, or learn more, [contact us](https://chain.link/contact). + -Once access is granted, you will be able to: +ACE offers three managers. Choose the path that matches what you need to do: -- Configure programmable compliance policies on your smart contracts using the **Policy Manager** -- Manage cross-chain identities and credentials using the **Identity Manager** -- Query onchain compliance state and transaction history using the **Reporting Manager** +|
Manager
| What it does | Get started | +| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| **Policy Manager** | Attach and configure compliance policies on smart contracts — volume limits, allowlists, RBAC, identity-based checks, and more | [Policy Manager Quick Start](/ace/getting-started/policy-manager) | +| **Identity Manager** | Manage identity registries, register cross-chain identities (CCIDs), and issue credentials such as Proof of Identity or accreditation attestations | [Identity Manager Quick Start](/ace/getting-started/identity-manager) | +| **Reporting Manager** | Query on-chain transaction history, policy configurations, and identity states via a read-only API for compliance verification | [Reporting concepts](/ace/concepts/reporting) — API Reference | -For an overview of ACE and its capabilities, see the [Overview](/ace) page and explore the [Core Concepts](/ace/concepts/architecture) section. +### Shared first step + +All three managers require **account setup** first — creating your organization, sharing your Org ID for enablement, generating an API key, and setting up [CRE Connect Wallets](/ace/concepts/key-terms#cre-connect-wallet): + +- [Account Setup](/ace/getting-started/account-setup) — the four steps every ACE user completes before using any manager + +### Not sure which Manager you need? + +- If you are a **token issuer or protocol team** deciding which compliance rules to enforce, start with the [Policy Manager Quick Start](/ace/getting-started/policy-manager). +- If you are an **identity provider (IDV)** or **credential issuer**, or **sanctions data provider** supplying data for others to consume, start with the [Identity Manager Quick Start](/ace/getting-started/identity-manager). +- Many organizations use **both managers**. Start with whichever is most relevant to your first use case — the guides cross-reference each other where the workflows intersect. + +### Background reading + +Before diving in, these pages provide essential context: + +- [Signing & Ownership Model](/ace/concepts/signing-ownership) — how ACE manages keys and the delegated and self-signing models +- [ACE Architecture](/ace/concepts/architecture) — system components and how they connect +- [Key Terms](/ace/concepts/key-terms) — ACE-specific terminology +- [Beta Scope](/ace/beta-scope) — what is and is not available during the Beta program diff --git a/src/content/ace/getting-started/policy-manager.mdx b/src/content/ace/getting-started/policy-manager.mdx new file mode 100644 index 00000000000..92f812497c8 --- /dev/null +++ b/src/content/ace/getting-started/policy-manager.mdx @@ -0,0 +1,277 @@ +--- +section: ace +title: "Policy Manager Quick Start" +date: Last Modified +metadata: + description: "Get started as a Policy Manager with Chainlink ACE: create your account, get your PolicyEngine address, integrate your smart contract, configure compliance policies, and test your first policy-protected transaction." + datePublished: "2026-04-15" + lastModified: "2026-05-26" +--- + +import { Aside, MethodTabs } from "@components" +import AceCallout from "@features/ace/callouts/AceCallout.astro" + + + +This guide walks you through the **Policy Manager** — the ACE component for attaching and configuring compliance policies on smart contracts. By the end you will have a policy-protected contract running on a supported network. + +### 1. Prerequisites + +- Solidity basics +- **Must-read before proceeding:** [Signing & Ownership Model](/ace/concepts/signing-ownership) — understand how ACE manages keys, who owns what, and how the delegated trust model works + +### 2. Account setup + +Complete the [Account Setup](/ace/getting-started/account-setup) steps — organization creation, API key generation, and CRE Connect Wallet deployment — before proceeding. + +### 3. Create a PolicyEngine + +A **PolicyEngine** is the onchain contract that evaluates compliance rules on your smart contract. When you create a PolicyEngine, you also attach **extractors** — modules that decode transaction calldata so the PolicyEngine can evaluate policies against function arguments (sender, recipient, amount, etc.). + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Policy Manager**. + 1. Click **Create a new engine**. + 1. Complete the three-step wizard: + - **Basic details** — enter an engine name and optional description. This metadata is internal and is not written onchain. + - **Networks** — select every chain where you want this engine deployed. Only networks enabled in your CRE account appear. Deploy a [CRE Connect Wallet](/ace/getting-started/account-setup#4-set-up-cre-connect-wallets) on each selected chain before creating the engine. + - **Contract types** — select **ERC-20** and/or **ERC-3643**. ACE attaches all pre-built extractors for the selected types automatically — you do not pick individual extractors in the UI. + 1. Submit the wizard and wait for onchain deployment to complete. + 1. Open the engine in Policy Manager and confirm its status is **Active**. Click the **gear icon** to copy the onchain PolicyEngine contract address for each network — you need these addresses in the next step. + + For full UI steps, see [Create a policy engine](/ace/guides/policy-manager/manage-engines#create-a-policy-engine). + + + + + + ACE Beta supports two contract types. Attach **all** extractors for your contract type at creation time so you don't have to add more later. + + #### Extractor IDs for API creation + + ##### ERC-20 extractors + + | Extractor | ID | Functions covered | + | -------------------------------------- | -------------------------------------- | --------------------------------------------------------------------- | + | ERC20TransferExtractor | `d4b8cd51-7d5a-487a-9ba5-bb7236e3184c` | `transfer(address,uint256)`, `transferFrom(address,address,uint256)` | + | ERC20ApproveExtractor | `572201bb-170b-4fda-ab89-a65b5bbc594b` | `approve(address,uint256)` | + | ComplianceTokenMintBurnExtractor | `245c24a9-6fdd-48e0-a418-725ed0b2056c` | `mint(address,uint256)`, `burnFrom(address,uint256)`, `burn(uint256)` | + | ComplianceTokenFreezeUnfreezeExtractor | `89df90a8-7f93-4ce1-9752-c0568062ae41` | `freeze(address,uint256,bytes)`, `unfreeze(address,uint256,bytes)` | + | ComplianceTokenForceTransferExtractor | `c360c6f1-1df5-43b3-bd53-ad7d4c0bba54` | `forceTransfer(address,address,uint256,bytes)` | + + ##### ERC-3643 extractors + + | Extractor | ID | Functions covered | + | -------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------- | + | ERC20TransferExtractor | `d4b8cd51-7d5a-487a-9ba5-bb7236e3184c` | `transfer(address,uint256)`, `transferFrom(address,address,uint256)` | + | ERC20ApproveExtractor | `572201bb-170b-4fda-ab89-a65b5bbc594b` | `approve(address,uint256)` | + | ERC3643MintBurnExtractor | `f17bbe8b-8462-4dd7-8fb7-a4973dff04fc` | `mint(address,uint256)`, `burn(address,uint256)` | + | ERC3643FreezeUnfreezeExtractor | `a6422540-76fb-46bc-90e2-16cc7b0aaa1e` | `freezePartialTokens(address,uint256)`, `unfreezePartialTokens(address,uint256)` | + | ERC3643SetAddressFrozenExtractor | `bd80dda1-fa6c-4028-a26e-a1802ae41ec7` | `setAddressFrozen(address,bool)` | + | ERC3643ForcedTransferExtractor | `d9015eeb-3345-478f-b2f1-2194b6689078` | `forcedTransfer(address,address,uint256)` | + + + + Use `POST /policy-engines` with the `extractor_ids` for your contract type. Deploy on all available networks upfront to avoid additional updates later (see [Supported Networks](/ace/supported-networks) for chain selectors). + + **For an ERC-20 contract:** + + ```bash + curl -X POST https://ace.api.chain.link/v1/policy-engines \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "name": "My Policy Engine", + "description": "Policy engine for my ERC-20 token", + "extractor_ids": [ + "d4b8cd51-7d5a-487a-9ba5-bb7236e3184c", + "572201bb-170b-4fda-ab89-a65b5bbc594b", + "245c24a9-6fdd-48e0-a418-725ed0b2056c", + "89df90a8-7f93-4ce1-9752-c0568062ae41", + "c360c6f1-1df5-43b3-bd53-ad7d4c0bba54" + ], + "onchain_policy_engines": [ + { "chain_selector": "16015286601757825753" }, + { "chain_selector": "3478487238524512106" }, + { "chain_selector": "14767482510784806043" }, + { "chain_selector": "16281711391670634445" }, + { "chain_selector": "10344971235874465080" } + ] + }' + ``` + + **For an ERC-3643 contract:** + + ```bash + curl -X POST https://ace.api.chain.link/v1/policy-engines \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "name": "My Policy Engine", + "description": "Policy engine for my ERC-3643 token", + "extractor_ids": [ + "d4b8cd51-7d5a-487a-9ba5-bb7236e3184c", + "572201bb-170b-4fda-ab89-a65b5bbc594b", + "f17bbe8b-8462-4dd7-8fb7-a4973dff04fc", + "a6422540-76fb-46bc-90e2-16cc7b0aaa1e", + "bd80dda1-fa6c-4028-a26e-a1802ae41ec7", + "d9015eeb-3345-478f-b2f1-2194b6689078" + ], + "onchain_policy_engines": [ + { "chain_selector": "16015286601757825753" }, + { "chain_selector": "3478487238524512106" }, + { "chain_selector": "14767482510784806043" }, + { "chain_selector": "16281711391670634445" }, + { "chain_selector": "10344971235874465080" } + ] + }' + ``` + + The response includes the PolicyEngine `id` and the onchain addresses for each chain: + + ```json + { + "id": "28755f4d-81c4-4df8-b424-a65782704d89", + "name": "My Policy Engine", + "type": "standard", + "chain_selectors": [ + "16015286601757825753", + "3478487238524512106", + "14767482510784806043", + "16281711391670634445", + "10344971235874465080" + ], + "onchain_policy_engines": [ + { + "id": "a3f7c2e1-9b04-4d6a-8e5f-1c2d3e4f5a6b", + "chain_selector": "16015286601757825753", + "address": "0x1234567890abcdef1234567890abcdef12345678", + "status": "creation_pending" + }, + { + "id": "b4e8d3f2-0c15-4e7b-9f6a-2d3e4f5a6b7c", + "chain_selector": "3478487238524512106", + "address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "status": "creation_pending" + } + // ... one entry per chain + ], + "extractor_registrations": [], + "created_at": 1775580000, + "updated_at": 1775580000 + } + ``` + + + + +#### Verify deployment + +The PolicyEngine and its extractors start with `"creation_pending"` / `"inactive"` status while the onchain transactions are processed. Poll until everything is ready: + +```bash +curl https://ace.api.chain.link/v1/policy-engines/ \ + -H "Authorization: Apikey " +``` + +Check two things in the response: + +1. **PolicyEngine deployed** — Every entry in `onchain_policy_engines` shows `"status": "created"`. +1. **Extractors active** — Every entry in `extractor_registrations[].onchain_extractor_registrations` shows `"status": "active"`. + +In the Platform UI, confirm the engine status is **Active** and extractors appear in the engine settings page. + + + +#### Save the PolicyEngine addresses + +Copy the `address` value from each entry in `onchain_policy_engines` — or from the engine settings page in the UI — you need these addresses to deploy or upgrade your smart contract in the next step. Each chain has a different PolicyEngine contract address. + +### 4. Integrate your contract + +See [Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible) for a full overview of what your contract needs. In short: + +- Inherit from `PolicyProtected` (or `PolicyProtectedUpgradeable` for upgradeable contracts) +- Add the `runPolicy` modifier to the functions you want to protect +- Pass the PolicyEngine address during deployment or initialization + +**Choose your path:** + +#### New contract + +If you are building a new token or contract from scratch, ACE provides reference implementations you can use as a starting point: + +- **ERC-20** — see [Building an ERC-20 Compliance Token](/ace/guides/policy-manager/contracts/erc20-token) for the full guide +- **ERC-3643** — see [Building an ERC-3643 Compliance Token](/ace/guides/policy-manager/contracts/erc3643-token) for the full guide + +#### Existing contract + +If you have an already-deployed contract you want to add ACE compliance to, this involves modifying your implementation contract, testing, and executing a proxy upgrade. This is typically the longest step in the onboarding process. + +- See [Upgrading Existing Contracts](/ace/guides/policy-manager/contracts/upgrade-existing) for the step-by-step guide +- Non-upgradeable contracts require alternative approaches — contact your Chainlink representative for guidance + +### 5. Register your contract as a target + +After deploying your contract, register it as a target under your PolicyEngine. Provide the contract name, type, protected methods, and on-chain addresses for each chain where the contract is deployed. + +```bash +curl -X POST https://ace.api.chain.link/v1/targets \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "title": "My ERC-20 Token", + "description": "Production ERC-20 token with compliance enforcement", + "policy_engine_id": "", + "protected_methods": [ + "transfer(address,uint256)", + "transferFrom(address,address,uint256)", + #[any other methods you want to protect] + ], + "desired_default_allow": true, + "metadata": {"contract_type": "ERC-20"}, + "onchain_targets": [ + { + "chain_selector": "16015286601757825753", + "address": "0xYourContractAddressOnSepolia" + }, + { + "chain_selector": "3478487238524512106", + "address": "0xYourContractAddressOnArbitrumSepolia" + }, + #[any other chains where your contract is deployed] + ] + }' +``` + +Include an entry in `onchain_targets` for every chain where you deployed the contract. Once registered, your target appears in the ACE Platform dashboard under your policy engine. + +For a full description of all fields and options, see [Managing Targets](/ace/guides/policy-manager/manage-targets#register-a-target). + +### 6. Post-setup checklist + +Before creating policies, confirm that every component is in the expected state. You can verify each of these with a single API call. + +|
Check
| What to look for | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| CRE Connect Wallets created | `GET /wallets` — every wallet shows `"status": "created"` | +| PolicyEngine deployed | `GET /policy-engines/` — every `onchain_policy_engines[].status` is `"created"` | +| Extractors active | Same response — every `extractor_registrations[].onchain_extractor_registrations[].status` is `"active"` | +| Contract visible in platform | Your target contract appears in the ACE Platform dashboard | + +### 7. Create and configure policies + +From the UI or API, create policy instances and attach them to your contract's protected functions. See [Managing Policies](/ace/guides/policy-manager/manage-policies) for creating and configuring policies, then [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) for attaching them to specific functions on your contracts. + +### 8. Test it + +Make a transaction against your protected contract, verify the policy enforces correctly, and check the results in the [Reporting Manager](/ace/concepts/reporting). diff --git a/src/content/ace/guides/identity-manager/external-registries.mdx b/src/content/ace/guides/identity-manager/external-registries.mdx new file mode 100644 index 00000000000..3b8d682a009 --- /dev/null +++ b/src/content/ace/guides/identity-manager/external-registries.mdx @@ -0,0 +1,153 @@ +--- +section: ace +title: "External Registries" +date: Last Modified +metadata: + description: "Share ACE identity and credential registries across organizations. Grant another organization read access to your registry, discover registries shared with you, and use them as credential sources in your policies." + datePublished: "2026-07-17" + lastModified: "2026-07-17" +--- + +import { Aside, MethodTabs } from "@components" + +**External registries** let one organization reuse another organization's [registry](/ace/guides/identity-manager/manage-registries) without re-issuing identities or credentials. The registry owner grants a second organization **read access**, and that organization can then reference the registry's identities and credentials — for example, to enforce KYC in its own policies using a KYC provider's registry. + +Access is granted per registry, is **read-only** for the recipient, and can be revoked at any time. + +## Roles and concepts + +|
Term
| Meaning | +| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Grantor** | The organization that **owns** the registry and grants access to it. | +| **Grantee** | The organization that **receives** read access to the registry. | +| **Access grant** | The link between a registry and a grantee organization. It is either `active` or `revoked`. | +| **Org ID** | The identifier of an organization. The grantee shares theirs with the grantor so the grantor can grant access. Retrieve it with `GET /organizations/me` (Coordinator API). | +| **`access_type`** | A field on a registry indicating whether the caller `owned` it or was `granted` access to it. | + +## What the grantee can and cannot do + +An active grant gives the grantee **read access** to the registry: + +- **Can** list and view the registry, and read its [credential types](/ace/guides/identity-manager/manage-credential-types), [identities](/ace/guides/identity-manager/manage-identities), and [credentials](/ace/guides/identity-manager/manage-credentials). +- **Can** reference the registry's on-chain contracts as a credential source in its own [identity-validation policies](/ace/reference/policy-library/credential-registry-identity-validator-policy). +- **Cannot** write to the registry — registering identities, issuing credentials, or changing configuration remains exclusive to the grantor. + + + +## Grant access to another organization + +Granting access requires the grantee's **Org ID**. Ask the grantee to retrieve it and share it with you: + +```bash +# Run by the grantee — returns their organization, including its id +curl https://ace.api.chain.link/v1/organizations/me \ + -H "Authorization: Apikey " +``` + +As the registry owner, create the grant: + +```bash +curl -X POST https://ace.api.chain.link/v1/registries//access-grants \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "grantee_org_id": "org-456" + }' +``` + +The response is the created grant: + +```json +{ + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "grantee_org_id": "org-456", + "grantor_org_id": "org-123", + "status": "active", + "granted_at": 1800000000, + "revoked_at": null +} +``` + +## View who has access + +List the active and past grants for a registry you own: + +```bash +curl https://ace.api.chain.link/v1/registries//access-grants \ + -H "Authorization: Apikey " +``` + +Each entry includes the grantee, the status (`active` or `revoked`), and the `granted_at` / `revoked_at` timestamps, giving you an audit trail of who was granted access and when. + +## Use a registry shared with you + +As a grantee, include `include_granted=true` when listing registries to see registries other organizations have shared with you, alongside your own: + +```bash +curl "https://ace.api.chain.link/v1/registries?include_granted=true" \ + -H "Authorization: Apikey " +``` + +Each registry in the response carries an **`access_type`** field: + +- `"owned"` — your organization owns the registry. +- `"granted"` — another organization (shown in `org_id`) granted you access. + +Once you can see a granted registry, you use it the same way you would reference any credential source: + +1. Read the registry to get the on-chain **identity registry** and **credential registry** contract addresses per chain, and read its **credential types** to get the `credential_type_hash` values you need. +1. Add those addresses and credential type hashes as a **credential source** on your [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) or [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) instance. + +Your policy then validates credentials issued by the other organization at transaction time. Because the grant is read-only, you rely on the grantor to keep the credentials current; if they revoke a credential, your policy sees the change immediately. + + + +## Revoke access + +As the registry owner, revoke a grant by setting its status to `revoked`. Identify the grant by the grantee's Org ID: + +```bash +curl -X PATCH https://ace.api.chain.link/v1/registries//access-grants/org-456 \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "status": "revoked" + }' +``` + +Revocation takes effect immediately. The grant record is retained with a `revoked_at` timestamp for audit purposes rather than deleted, so the history of grants and revocations is preserved. To restore access later, create a new grant. + +## What happens when access is revoked + +Revoking a registry access grant is a **platform-level action only** — it removes the registry from the grantee's view in the ACE Platform (UI and API). The grantee can no longer browse the registry, read its credentials, or reference it in new policy configurations. + +**Onchain, nothing changes automatically.** If the grantee's policies already reference the revoked registry's onchain contracts as a credential source, those policies continue to validate credentials from that registry at transaction time. The onchain policy contracts have no awareness of platform-level access grants — they only know the registry contract addresses that were configured as credential sources. + +### What each party should do + +**Grantor** — After revoking access, be aware that the grantee's existing policies may still reference your registry onchain. + +**Grantee** — After a grant is revoked, the ACE Platform displays a warning on any policy instance that references a source from the revoked registry. You should remove the revoked registry source from your policy configuration to ensure your compliance setup reflects the current state of your access agreements. Until you remove it: + +- The policy continues to validate credentials from the revoked registry onchain. +- You cannot edit the revoked source — you can only remove it. +- You cannot reference the revoked registry in new policy configurations. + + + +## Related pages + +- [Managing Registries](/ace/guides/identity-manager/manage-registries) — create and manage the registries you own +- [Cross-Chain Identity](/ace/concepts/cross-chain-identity) — CCIDs, registries, and credential sources +- [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) — reference a registry as a credential source +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — configure policy instances and their credential sources +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema diff --git a/src/content/ace/guides/identity-manager/index.mdx b/src/content/ace/guides/identity-manager/index.mdx new file mode 100644 index 00000000000..6295cf1dcb9 --- /dev/null +++ b/src/content/ace/guides/identity-manager/index.mdx @@ -0,0 +1,27 @@ +--- +section: ace +title: "Identity Manager Guides" +isIndex: true +date: Last Modified +metadata: + description: "Operational guides for Identity Managers using Chainlink ACE: manage registries, register cross-chain identities, define credential types, and issue credentials." + datePublished: "2026-04-06" + lastModified: "2026-04-06" +--- + +import { Aside } from "@components" + +These guides cover the day-to-day operations of an Identity Manager — from setting up registries to issuing and managing credentials across chains. + + + +## Available guides + +- [Managing Registries](/ace/guides/identity-manager/manage-registries) — create, view, import, and archive identity and credential registry pairs +- [Managing Identities](/ace/guides/identity-manager/manage-identities) — register cross-chain identities (CCIDs), map wallet addresses across chains, and manage identity lifecycle +- [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types) — define the categories of credentials your registry supports (e.g., KYC, accreditation, sanctions clearance) +- [Managing Credentials](/ace/guides/identity-manager/manage-credentials) — issue, renew, revoke, and set expiration on credentials using the attestation model +- [External Registries](/ace/guides/identity-manager/external-registries) — share a registry with another organization, and use registries shared with you as credential sources diff --git a/src/content/ace/guides/identity-manager/manage-credential-types.mdx b/src/content/ace/guides/identity-manager/manage-credential-types.mdx new file mode 100644 index 00000000000..1b8f8c5882e --- /dev/null +++ b/src/content/ace/guides/identity-manager/manage-credential-types.mdx @@ -0,0 +1,167 @@ +--- +section: ace +title: "Managing Credential Types" +date: Last Modified +metadata: + description: "Define, view, and manage credential types in Chainlink ACE using the Coordinator API." + datePublished: "2026-04-06" + lastModified: "2026-07-17" +--- + +import { Aside } from "@components" + +Credential types define the categories of attestations you can issue to [cross-chain identities (CCIDs)](/ace/concepts/cross-chain-identity). Each credential type represents a distinct kind of verification — for example, KYC completion, accredited investor status, or sanctions clearance. When you create a credential type, the `credential_type` string you provide is hashed to produce a `credential_type_hash` that policy contracts reference on-chain. + +Credential types are scoped to a specific credential registry. Before creating credential types, make sure your [registries are set up](/ace/guides/identity-manager/manage-registries). + +A credential type can optionally be linked to a **data schema**, which lets the credentials you issue against it carry structured data (for example, a jurisdiction code). See [Typed credentials with data schemas](#typed-credentials-with-data-schemas) below. + +## What is a credential type? + +A credential type is a string you define to represent a specific compliance check or verification. This string is hashed and registered on-chain, so it cannot be changed after creation. You can create any credential types that match your requirements — for example: + +| Credential type string | Use case | +| ---------------------- | -------------------------------------- | +| `PROOF_OF_IDENTITY` | Identity verification | +| `PROOF_OF_FUNDS` | Source of funds or reserves check | +| `AML_CHECK` | Anti-money-laundering screening result | + +The `credential_type` string is case-sensitive and must be unique within a registry. + +## Define a credential type + +Register a credential type with a `POST` request: + +```bash +curl -X POST "https://ace.api.chain.link/v1/credential-types" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "registry_id": "", + "title": "KYC Verification", + "credential_type": "KYC", + "description": "Basic Know Your Customer identity verification" + }' +``` + +The response includes the generated `credential_type_id` and the `credential_type_hash` derived from your `credential_type` string. + +## Understand credential type hashes + +When you create a credential type, ACE hashes the `credential_type` string to produce a deterministic `credential_type_hash`. This hash is what gets written on-chain and what policy contracts use when evaluating identity-based rules. + +```text +credential_type string → credential_type_hash → on-chain reference +"KYC" → 0x7a8b...3f21 → used by policy contracts +``` + +Because the hash is derived from the string, choosing your `credential_type` strings carefully matters — they cannot be changed after creation. Policy contracts such as the [Credential Registry Identity Validator](/ace/reference/policy-library/credential-registry-identity-validator-policy) reference credentials by their `credential_type_hash` when checking whether an identity holds a required attestation. + + + +## Typed credentials with data schemas + +By default, credentials are **attestation-only**: they record that an identity holds a credential of a given type, with no additional data. You can instead create a **typed** credential type by linking it to a **data schema**. Credentials issued against a typed credential type carry structured data (validated against the schema), which policies can then evaluate through a [Data Validator](/ace/guides/policy-manager/manage-data-validators). + +A **data schema** is a reusable definition of the shape and format of a credential's data. ACE provides shared, ready-to-use schemas — the first is an [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code schema for jurisdiction use cases (an array of two-letter country codes such as `US`, `CA`, `GB`). + +To make a credential type typed, pass a `data_schema_id` when you create it. The ISO 3166-1 alpha-2 country code data schema ID is: + +```text +fb786cd7-6397-4ac6-790c-35746f343cad +``` + +```bash +curl -X POST "https://ace.api.chain.link/v1/credential-types" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "registry_id": "", + "title": "Jurisdiction", + "credential_type": "common.country", + "description": "Holder jurisdiction as ISO 3166-1 alpha-2 country codes", + "data_schema_id": "fb786cd7-6397-4ac6-790c-35746f343cad" + }' +``` + +Once a credential type is linked to a data schema, every credential you issue against it **must** include `credential_data` matching that schema — see [Issue a credential with data](/ace/guides/identity-manager/manage-credentials#issue-a-credential-with-data). + + + +## View credential types + +List credential types with a `GET` request. Use the `registry_id` query parameter to filter by registry: + +```bash +curl "https://ace.api.chain.link/v1/credential-types?registry_id=&page=1&page_size=25" \ + -H "Authorization: Apikey " +``` + +To retrieve a single credential type by ID: + +```bash +curl "https://ace.api.chain.link/v1/credential-types/" \ + -H "Authorization: Apikey " +``` + +## Update a credential type + +You can update a credential type's **title** and **description**. The `credential_type` string and `credential_type_hash` cannot be changed. + +Update a credential type with a `PUT` request: + +```bash +curl -X PUT "https://ace.api.chain.link/v1/credential-types/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "title": "KYC Verification (Enhanced)", + "description": "Enhanced KYC verification including document and liveness checks" + }' +``` + +You can also make partial updates with a `PATCH` request: + +```bash +curl -X PATCH "https://ace.api.chain.link/v1/credential-types/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "description": "Updated description for KYC verification" + }' +``` + +## Archive a credential type + +Archiving a credential type prevents new credentials of that type from being issued. Existing credentials remain valid until they are individually archived or expire. + + + +Archive a credential type with a `PATCH` request: + +```bash +curl -X PATCH "https://ace.api.chain.link/v1/credential-types/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "status": "archived" + }' +``` + +If active credentials still reference the type, the request returns an error. Archive all associated credentials first, then retry. + +## Related resources + +- [Cross-Chain Identity](/ace/concepts/cross-chain-identity) — conceptual overview of CCIDs, registries, and credential types +- [Managing Credentials](/ace/guides/identity-manager/manage-credentials) — issue, revoke, and manage credentials linked to CCIDs +- [Managing Registries](/ace/guides/identity-manager/manage-registries) — view and manage identity and credential registry deployments +- [Credential Registry Identity Validator Policy](/ace/reference/policy-library/credential-registry-identity-validator-policy) — the policy contract that checks credentials on-chain diff --git a/src/content/ace/guides/identity-manager/manage-credentials.mdx b/src/content/ace/guides/identity-manager/manage-credentials.mdx new file mode 100644 index 00000000000..09043249a12 --- /dev/null +++ b/src/content/ace/guides/identity-manager/manage-credentials.mdx @@ -0,0 +1,219 @@ +--- +section: ace +title: "Managing Credentials" +date: Last Modified +metadata: + description: "Issue, renew, revoke, and manage credentials in Chainlink ACE using the Coordinator API." + datePublished: "2026-04-06" + lastModified: "2026-07-17" +--- + +import { Aside } from "@components" + +Credentials are attestations that a [cross-chain identity (CCID)](/ace/concepts/cross-chain-identity) holds a specific qualification — for example, KYC verification, accredited investor status, or sanctions clearance. Each credential links a **credential type** to an **identity** and is recorded on-chain across every chain where the credential registry is deployed. + +This guide covers the full credential lifecycle: issuing, viewing, updating, expiring, and revoking credentials through the Coordinator API. + +## Attestation vs. typed credentials + +ACE supports two kinds of credentials: + +- **Attestation-only** (the default) — The credential records only that an identity holds a credential of a given type, with no additional data. When you issue one, the on-chain record contains just the **credential type hash**, the **identity** (CCID) it is issued to, and an **issuance timestamp**. Policies verify existence — for example, "does this address have a valid KYC credential?" — without accessing any personally identifiable information (PII). +- **Typed** — When the credential type is linked to a [data schema](/ace/guides/identity-manager/manage-credential-types#typed-credentials-with-data-schemas), the credential also carries structured `credential_data` (for example, a jurisdiction code). Policies can then evaluate the contents through a [Data Validator](/ace/guides/policy-manager/manage-data-validators), not just the credential's existence. + +In both cases, no PII should be stored on-chain — credential data must be a minimal, non-sensitive value (such as an ISO country code) or a hash. For a deeper discussion of credential data and privacy, see [Credential Data and Privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy). + +## Issue a credential + +To issue a credential, you need a registered [identity](/ace/guides/identity-manager/manage-identities) and at least one [credential type](/ace/guides/identity-manager/manage-credential-types) defined in your registry. + + + +Issue a credential with a `POST` request: + +```bash +curl -X POST "https://ace.api.chain.link/v1/credentials" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "credential_type_id": "", + "identity_id": "", + "external_unique_id": "kyc-2026-04-acme", + "expires_at": 1806883200 + }' +``` + +| Field | Required | Description | +| -------------------- | -------- | ------------------------------------------------------------ | +| `credential_type_id` | Yes | UUID of the credential type to issue | +| `identity_id` | Yes | UUID of the target identity (CCID) | +| `external_unique_id` | No | Your own reference identifier for this credential | +| `expires_at` | No | Unix timestamp (integer); omit for a non-expiring credential | + +## Issue a credential with data + +When the credential type is linked to a [data schema](/ace/guides/identity-manager/manage-credential-types#typed-credentials-with-data-schemas), include a `credential_data` field. The value must match the schema — for the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code schema, that is a JSON array of two-letter country codes. ACE validates the data against the schema and encodes it on-chain. + +```bash +curl -X POST "https://ace.api.chain.link/v1/credentials" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "credential_type_id": "", + "identity_id": "", + "credential_data": ["US"], + "external_unique_id": "jurisdiction-2026-04-acme", + "expires_at": 1806883200 + }' +``` + + + +Once issued, the credential data can be enforced at transaction time by attaching a [Data Validator](/ace/guides/policy-manager/manage-data-validators) to the credential source of an identity-validation policy. + +## Issue credentials during identity creation + +You can issue credentials inline when registering a new identity by including a `credentials` array in the `POST /identities` request body. This is useful when you have completed verification before registration and want to create the identity and its credentials in a single call. + +```bash +curl -X POST "https://ace.api.chain.link/v1/identities" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Acme Corp Treasury", + "entity_id": "acme-corp-001", + "registry_id": "", + "onchain_identities": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890abcdef1234567890abcdef12345678" + } + ], + "credentials": [ + { + "credential_type_id": "", + "external_unique_id": "kyc-2026-04-acme", + "expires_at": 1806883200 + }, + { + "credential_type_id": "" + } + ] + }' +``` + +Each entry in the `credentials` array follows the same schema as the standalone `POST /credentials` endpoint, except that `identity_id` is inferred from the identity being created. See [Managing Identities](/ace/guides/identity-manager/manage-identities) for the full identity creation reference. + +## View and filter credentials + +List credentials with a `GET` request. All query parameters are optional: + +```bash +curl "https://ace.api.chain.link/v1/credentials?credential_type_id=&page=1&page_size=25" \ + -H "Authorization: Apikey " +``` + +| Parameter | Description | +| -------------------- | --------------------------------------------------- | +| `credential_type_id` | Filter by credential type | +| `identity_id` | Filter by identity | +| `entity_id` | Filter by entity | +| `registry_id` | Filter by registry | +| `include_onchains` | Include on-chain deployment details in the response | +| `page` | Page number (default: 1) | +| `page_size` | Results per page | + +To retrieve a single credential by its ID: + +```bash +curl "https://ace.api.chain.link/v1/credentials/" \ + -H "Authorization: Apikey " +``` + +## Credential expiration + +The `expires_at` field controls whether a credential has a limited validity period. + +- **No expiration** — Omit `expires_at` when issuing. The credential remains valid indefinitely until explicitly archived. +- **With expiration** — Provide a Unix timestamp (integer). Once the timestamp passes, policy checks that require this credential type will treat the credential as invalid. + +To **renew** an expiring credential, update it with a new `expires_at` value (see the next section). Alternatively, you can archive the expired credential and issue a new one. + + + +## Update a credential + +You can update a credential's `external_unique_id` and `expires_at` fields. You **cannot** change the credential type or the associated identity — to change either, archive the credential and issue a new one. + +Update a credential with a `PUT` request: + +```bash +curl -X PUT "https://ace.api.chain.link/v1/credentials/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "external_unique_id": "kyc-2026-04-acme-renewed", + "expires_at": 1838419200 + }' +``` + +| Field | Required | Description | +| -------------------- | -------- | ------------------------------------------------- | +| `external_unique_id` | Yes | Updated reference identifier | +| `expires_at` | No | New expiration timestamp; omit to leave unchanged | + +You can also perform a partial update with `PATCH`. The `PATCH` endpoint accepts `external_unique_id` and `expires_at` independently, but you **cannot** combine field updates with a status change in the same request: + +```bash +curl -X PATCH "https://ace.api.chain.link/v1/credentials/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "expires_at": 1838419200 + }' +``` + +## Revoke (archive) a credential + +Archiving a credential removes it from the on-chain credential registry. After archival, policy contracts will no longer see this credential — any policy that requires it (such as the [Credential Registry Identity Validator](/ace/reference/policy-library/credential-registry-identity-validator-policy)) will reject transactions from the associated addresses. + +Common reasons to revoke a credential: + +- KYC verification expired or failed re-verification +- Sanctions status changed +- Accreditation lapsed +- Entity relationship terminated + +Archive a credential with a `PATCH` request: + +```bash +curl -X PATCH "https://ace.api.chain.link/v1/credentials/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "status": "archived" + }' +``` + + + +## Related resources + +- [Cross-Chain Identity](/ace/concepts/cross-chain-identity) — CCID model, credential registries, and the attestation lifecycle +- [Managing Identities](/ace/guides/identity-manager/manage-identities) — register and manage CCIDs and their on-chain address mappings +- [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types) — create and organize the credential categories your registry supports +- [Credential Registry Identity Validator Policy](/ace/reference/policy-library/credential-registry-identity-validator-policy) — the policy that checks credentials at transaction time +- [Managing Data Validators](/ace/guides/policy-manager/manage-data-validators) — enforce rules on credential data at transaction time +- [Beta Scope](/ace/beta-scope) — current scope and limitations diff --git a/src/content/ace/guides/identity-manager/manage-identities.mdx b/src/content/ace/guides/identity-manager/manage-identities.mdx new file mode 100644 index 00000000000..617b8d5e468 --- /dev/null +++ b/src/content/ace/guides/identity-manager/manage-identities.mdx @@ -0,0 +1,332 @@ +--- +section: ace +title: "Managing Identities" +date: Last Modified +metadata: + description: "Register, update, and manage cross-chain identities (CCIDs) in Chainlink ACE using the Platform UI or the Coordinator API." + datePublished: "2026-03-31" + lastModified: "2026-04-06" +--- + +import { Aside, MethodTabs } from "@components" + +This guide covers how to register, view, update, and archive cross-chain identities (CCIDs) using the ACE Platform UI or the Coordinator API. Identities are the foundation of ACE's credential system — every credential is issued against an identity. + + + +## What are identities (CCIDs)? + +A **cross-chain identity (CCID)** aggregates multiple wallet addresses across EVM chains into a single logical entity. Rather than treating each address on each chain as a separate user, ACE maps them all to one CCID. Credentials issued against that CCID are then valid for every linked address on every chain — no re-issuance or bridging required. + +Each identity includes: + +- **Title** — A human-readable label for internal use only (e.g., "Jane Doe"). This value is never written on-chain. +- **Entity ID** — A unique external identifier that ties the identity back to your system of record (e.g., a KYC provider user ID). This value must be unique within a registry. +- **Registry** — The registry the identity belongs to. +- **On-chain identities** — One or more wallet address + chain selector pairs that map to this CCID on-chain. + +For a deeper look at how CCIDs work, how they are generated, and the privacy considerations involved, see [Cross-Chain Identity](/ace/concepts/cross-chain-identity). + +## Register an identity + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Identity Manager** and click on the registry you want to add an identity to. + 1. In the **Identities** tab, click **+ Add identity**. + 1. **Step 1 — Identity details:** Enter an alias and optional description for this identity. This metadata is for internal use only and is never written on-chain. + 1. **Step 2 — Add wallets:** Add the wallet address(es) for this identity on each chain where you need them. Your registry must be deployed on these chains. + 1. **Step 3 — Assign credentials (optional):** Optionally assign credentials to this identity, along with an optional expiration date for each. + + + Send a `POST` request to `/identities`: + + ```bash + curl -X POST "https://ace.api.chain.link/v1/identities" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Jane Doe", + "entity_id": "jane-doe-001", + "registry_id": "a1b2c3d4-5678-9abc-def0-1234567890ab", + "description": "Jane Doe's multi-chain wallets", + "onchain_identities": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890abcdef1234567890abcdef12345678" + }, + { + "chain_selector": "3478487238524512106", + "address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + } + ] + }' + ``` + + You can also issue credentials inline at creation time by including a `credentials` array. This is useful when you already have the credential types and want to skip a separate issuance step: + + ```bash + curl -X POST "https://ace.api.chain.link/v1/identities" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Jane Doe", + "entity_id": "jane-doe-001", + "registry_id": "a1b2c3d4-5678-9abc-def0-1234567890ab", + "onchain_identities": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890abcdef1234567890abcdef12345678" + } + ], + "credentials": [ + { + "credential_type_id": "f0e1d2c3-b4a5-6789-0123-456789abcdef", + "expires_at": 1800000000 + } + ] + }' + ``` + + See [Managing Credentials](/ace/guides/identity-manager/manage-credentials) for the full credential issuance workflow. + + + + +## Bulk import identities + +When onboarding many users at once, use the batch endpoint to create multiple identities in a single atomic request. Each identity in the batch follows the same schema as the single-create endpoint, including the optional `credentials` array — so you can register identities and issue credentials in one call. + +This feature is **API-only**. In the Platform UI, the **Bulk import via API** option under **+ Add identity** links to this documentation. + +Send a `POST` request to `/identities/batch`: + +```bash +curl -X POST "https://ace.api.chain.link/v1/identities/batch" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "identities": [ + { + "title": "Identity A", + "entity_id": "user-001", + "registry_id": "", + "onchain_identities": [ + { + "address": "0x1111111111111111111111111111111111111111", + "chain_selector": "16015286601757825753" + } + ], + "credentials": [ + { + "credential_type_id": "", + "expires_at": 1800000000 + } + ] + }, + { + "title": "Identity B", + "entity_id": "user-002", + "registry_id": "", + "onchain_identities": [ + { + "address": "0x2222222222222222222222222222222222222222", + "chain_selector": "16015286601757825753" + }, + { + "address": "0x3333333333333333333333333333333333333333", + "chain_selector": "3478487238524512106" + } + ] + } + ] + }' +``` + +The `credentials` array is optional on each identity. The second identity in this example is created without credentials. + + + +## View and search identities + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Identity Manager** and click on the registry you want to browse. + 1. The **Identities** tab displays a list of all identities with their alias, CCID, and the networks they are deployed on. You can search by name or filter by network using the controls at the top. + 1. Click on any identity to view its details: + - **Credentials** — the credentials assigned to this identity. + - **Wallets** — the wallet addresses registered for this identity on each network. + + + Send a `GET` request to `/identities`: + + ```bash + curl -X GET "https://ace.api.chain.link/v1/identities?page=1&page_size=25&include_onchains=true" \ + -H "Authorization: Apikey " + ``` + + To search for identities by title, use the `query` parameter: + + ```bash + curl -X GET "https://ace.api.chain.link/v1/identities?query=jane&include_onchains=true" \ + -H "Authorization: Apikey " + ``` + + To retrieve a specific identity by ID: + + ```bash + curl -X GET "https://ace.api.chain.link/v1/identities/98765432-10fe-dcba-9876-543210fedcba" \ + -H "Authorization: Apikey " + ``` + + + + +## Update an identity + +You can update an identity's title, description, and on-chain address mappings. ACE offers two update approaches: full replacement and partial update. + + + + 1. Open the identity detail page (see [View and search identities](#view-and-search-identities) above). + 1. Click the **Edit** button next to the alias name. + 1. A drawer opens where you can edit the alias and description. + + + + + + ### Full update (PUT) + + A `PUT` request replaces the identity's mutable fields entirely. The `onchain_identities` array you provide becomes the complete set of address mappings — any existing mappings not included in the request are removed. + + ```bash + curl -X PUT "https://ace.api.chain.link/v1/identities/98765432-10fe-dcba-9876-543210fedcba" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Jane Doe (Updated)", + "onchain_identities": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890abcdef1234567890abcdef12345678" + }, + { + "chain_selector": "3478487238524512106", + "address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + }, + { + "chain_selector": "10344971235874465080", + "address": "0x5555555555555555555555555555555555555555" + } + ] + }' + ``` + + + + ### Partial update (PATCH) + + A `PATCH` request updates only the fields you include. Use this when you want to change the title or description without touching the on-chain mappings. + + ```bash + curl -X PATCH "https://ace.api.chain.link/v1/identities/98765432-10fe-dcba-9876-543210fedcba" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Jane Doe — Primary", + "description": "Updated description for primary treasury wallets" + }' + ``` + + + + + + +## Cross-chain identity mapping + +A single CCID can span as many chains and addresses as needed. This is the core value proposition of ACE's identity model: one credential verification applies everywhere. + +For example, an entity operating wallets on Ethereum Sepolia, Arbitrum Sepolia, and Base Sepolia would have a single identity with three on-chain mappings: + +```bash +curl -X POST "https://ace.api.chain.link/v1/identities" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Multi-Chain Operator", + "entity_id": "operator-xyz-007", + "registry_id": "a1b2c3d4-5678-9abc-def0-1234567890ab", + "onchain_identities": [ + { + "chain_selector": "16015286601757825753", + "address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + { + "chain_selector": "3478487238524512106", + "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + { + "chain_selector": "10344971235874465080", + "address": "0xcccccccccccccccccccccccccccccccccccccccc" + } + ] + }' +``` + +| Chain | Chain Selector | Address | +| :--------------- | :--------------------- | :-------------- | +| Ethereum Sepolia | `16015286601757825753` | `0xaaaa...aaaa` | +| Arbitrum Sepolia | `3478487238524512106` | `0xbbbb...bbbb` | +| Base Sepolia | `10344971235874465080` | `0xcccc...cccc` | + +All three addresses resolve to the same CCID. A credential issued against this identity — such as a KYC attestation — is valid for all three addresses across all three chains. When any of these addresses interacts with a policy-protected contract, the policy resolves the address to the shared CCID and checks credentials from there. + +To add or remove chains later, use the [full update (PUT)](#full-update-put) endpoint with the updated list of on-chain identities. + +## Archive an identity + +Archiving marks an identity as inactive. Archived identities are retained for audit purposes but are no longer considered active. + + + + + + + Send a `PATCH` request with the `status` field: + + ```bash + curl -X PATCH "https://ace.api.chain.link/v1/identities/98765432-10fe-dcba-9876-543210fedcba" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "status": "archived" + }' + ``` + + + + + diff --git a/src/content/ace/guides/identity-manager/manage-registries.mdx b/src/content/ace/guides/identity-manager/manage-registries.mdx new file mode 100644 index 00000000000..7fb2f0f5982 --- /dev/null +++ b/src/content/ace/guides/identity-manager/manage-registries.mdx @@ -0,0 +1,260 @@ +--- +section: ace +title: "Managing Registries" +date: Last Modified +metadata: + description: "Create, view, and manage identity and credential registries in Chainlink ACE using the Platform UI or the Coordinator API." + datePublished: "2026-04-06" + lastModified: "2026-04-06" +--- + +import { Aside, MethodTabs } from "@components" + +## What are registries? + +A **registry** in ACE is the top-level organizational unit for the Identity Manager. Each registry bundles two types of sub-registries: + +- **Identity registries** — map wallet addresses to [Cross-Chain Identifiers (CCIDs)](/ace/concepts/cross-chain-identity#the-cross-chain-identifier-ccid). +- **Credential registries** — manage the lifecycle of credentials linked to CCIDs. + +Each sub-registry corresponds to a smart contract deployed on a specific blockchain. A single registry can span multiple chains by including sub-registries on each target network. For a deeper explanation of the registry model and how it fits into the identity lifecycle, see [Cross-Chain Identity](/ace/concepts/cross-chain-identity). + +## Create a registry + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Identity Manager** in the left sidebar. + 1. Click **Add a new registry**. + 1. **Step 1 — Registry details:** Set your registry's name and description. + 1. **Step 2 — Select networks:** Choose the networks you want your registry to be deployed to. + 1. Click **Deploy registry**. + + + Use the Coordinator API to create a registry with identity and credential sub-registries. Each entry in `identity_registries` and `credential_registries` specifies a `chain_selector` for the target network (see [Supported Networks](/ace/supported-networks) for chain selector values). + + This example creates a registry with sub-registries on Ethereum Sepolia and Arbitrum Sepolia: + + ```bash + curl -X POST https://ace.api.chain.link/v1/registries \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "Production Identity Registry", + "description": "Identity and credential registries for compliance verification", + "identity_registries": [ + { "name": "Identity Registry - Sepolia", "description": "Identity registry on Ethereum Sepolia", "chain_selector": "16015286601757825753" }, + { "name": "Identity Registry - Arbitrum", "description": "Identity registry on Arbitrum Sepolia", "chain_selector": "3478487238524512106" } + ], + "credential_registries": [ + { "name": "Credential Registry - Sepolia", "description": "Credential registry on Ethereum Sepolia", "chain_selector": "16015286601757825753" }, + { "name": "Credential Registry - Arbitrum", "description": "Credential registry on Arbitrum Sepolia", "chain_selector": "3478487238524512106" } + ] + }' + ``` + + When you omit the `address` field, ACE deploys a new managed contract on that chain. To import an existing contract instead, provide its address — see [Import an existing registry](#import-an-existing-registry). + + The response includes the registry ID and the status of each sub-registry deployment: + + ```json + { + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "name": "Production Identity Registry", + "description": "Identity and credential registries for compliance verification", + "org_id": "org-123", + "mode": "managed", + "chain_selectors": ["16015286601757825753", "3478487238524512106"], + "identity_registries": [ + { + "id": "d4c3b2a1-f6e5-0987-dcba-0987654321fe", + "name": "Identity Registry - Sepolia", + "description": "Identity registry on Ethereum Sepolia", + "chain_selector": "16015286601757825753", + "address": "0x1a2b3c4d5e6f7890abcdef1234567890abcdef12", + "registry_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "org_id": "org-123", + "status": "created", + "created_at": 1775580000, + "updated_at": 1775580000 + }, + { + "id": "e5d4c3b2-a1f6-8907-edcb-a09876543210", + "name": "Identity Registry - Arbitrum", + "description": "Identity registry on Arbitrum Sepolia", + "chain_selector": "3478487238524512106", + "address": "0x2b3c4d5e6f7890abcdef1234567890abcdef1234", + "registry_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "org_id": "org-123", + "status": "created", + "created_at": 1775580000, + "updated_at": 1775580000 + } + ], + "credential_registries": [ + { + "id": "f6e5d4c3-b2a1-9078-fedc-ba0987654321", + "name": "Credential Registry - Sepolia", + "description": "Credential registry on Ethereum Sepolia", + "chain_selector": "16015286601757825753", + "address": "0x3c4d5e6f7890abcdef1234567890abcdef123456", + "registry_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "org_id": "org-123", + "status": "created", + "created_at": 1775580000, + "updated_at": 1775580000 + }, + { + "id": "07f6e5d4-c3b2-1098-0fed-cb1098765432", + "name": "Credential Registry - Arbitrum", + "description": "Credential registry on Arbitrum Sepolia", + "chain_selector": "3478487238524512106", + "address": "0x4d5e6f7890abcdef1234567890abcdef12345678", + "registry_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "org_id": "org-123", + "status": "created", + "created_at": 1775580000, + "updated_at": 1775580000 + } + ], + "archived_at": null, + "created_at": 1775580000, + "updated_at": 1775580000 + } + ``` + + + + +## View registries + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Identity Manager** in the left sidebar. You see all registries in your organization. + 1. Click on a registry to open it. The detail view has two tabs: + - **Identities** — lists the identities registered in this registry. + - **Credential types** — lists the credential types configured for this registry. + + + List all registries: + + ```bash + curl https://ace.api.chain.link/v1/registries \ + -H "Authorization: Apikey " + ``` + + Use query parameters to paginate and filter results: + + | Parameter | Description | + | :----------------- | :------------------------------------------------------------------------- | + | `page` | Page number (default: 1) | + | `page_size` | Results per page | + | `include_onchains` | Include onchain sub-registry details in the response | + | `type` | Filter by type: `pair` (default), `identity`, or `credential` | + | `include_granted` | When `true`, also include registries other organizations have shared with you. Each registry carries an `access_type` of `owned` or `granted`. See [External Registries](/ace/guides/identity-manager/external-registries). | + + To retrieve a specific registry by ID: + + ```bash + curl https://ace.api.chain.link/v1/registries/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + -H "Authorization: Apikey " + ``` + + Use the `type` query parameter to filter by sub-registry type: + + ```bash + curl "https://ace.api.chain.link/v1/registries/a1b2c3d4-e5f6-7890-abcd-ef1234567890?type=identity" \ + -H "Authorization: Apikey " + ``` + + + + +## Multi-chain setup + +Registries are designed to work across multiple chains. Each entry in `identity_registries` and `credential_registries` targets a specific `chain_selector`, and the platform deploys (or imports) a contract on each specified chain independently. + +A typical multi-chain configuration: + +- **Identity registries** on every chain where users interact — so the IdentityRegistry on each chain can resolve wallet addresses to CCIDs locally. +- **Credential registries** on every chain where policies need to verify credentials at runtime. + +Because CCIDs are chain-agnostic identifiers, a credential issued on one chain's CredentialRegistry is logically valid across all chains. The multi-chain deployment ensures that each chain has a local copy of the registry contracts for low-latency, on-chain lookups. For more on how this model works, see [Cross-Chain Identity](/ace/concepts/cross-chain-identity). + + + +## Update a registry + +You can update a registry's name, description, and add new sub-registries. Updates are **additive** — you can add new identity or credential sub-registries to additional chains, but you cannot remove existing sub-registry pairs. + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Identity Manager** and click on the registry you want to update. + 1. Click on the gear icon to open the settings page. Edit the registry's **name** or **description**. + + + + + + ```bash + curl -X PUT https://ace.api.chain.link/v1/registries/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "Updated Registry Name", + "description": "Updated description with expanded chain coverage", + "identity_registries": [ + { "name": "Identity Registry - Base", "description": "Identity registry on Base Sepolia", "chain_selector": "10344971235874465080" } + ], + "credential_registries": [ + { "name": "Credential Registry - Base", "description": "Credential registry on Base Sepolia", "chain_selector": "10344971235874465080" } + ] + }' + ``` + + This example adds sub-registries on Base Sepolia to an existing registry. The existing sub-registries on other chains are unaffected. + + + + +## Share across organizations + +You can grant another organization read access to a registry you own, so it can use your identities and credentials as a credential source without re-issuing them. Grants are read-only for the recipient and can be revoked at any time. For the full workflow — granting, discovering registries shared with you, using them, and revoking — see [External Registries](/ace/guides/identity-manager/external-registries). + +## Archive a registry + +Archiving a registry deactivates it and prevents any further operations. Before archiving, all identities associated with the registry must be removed or archived first. + + + + + + + + + ```bash + curl -X PATCH https://ace.api.chain.link/v1/registries/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "status": "archived" + }' + ``` + + + +## Related pages + +- [Cross-Chain Identity](/ace/concepts/cross-chain-identity) — conceptual background on CCIDs, registries, and credential sources +- [Managing Identities and Credentials](/ace/guides/identity-manager/manage-identities) — register identities and issue credentials within a registry +- [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types) — define and manage the credential types used in credential registries +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema and parameters diff --git a/src/content/ace/guides/policy-manager/contracts/ace-compatible.mdx b/src/content/ace/guides/policy-manager/contracts/ace-compatible.mdx new file mode 100644 index 00000000000..e5039b306ed --- /dev/null +++ b/src/content/ace/guides/policy-manager/contracts/ace-compatible.mdx @@ -0,0 +1,75 @@ +--- +section: ace +title: "Making Your Contract ACE-Compatible" +date: Last Modified +metadata: + description: "Learn what your smart contract needs to be ACE-compatible: inheriting PolicyProtected, adding the runPolicy modifier, connecting to a PolicyEngine, and registering extractors." + datePublished: "2026-03-31" + lastModified: "2026-05-26" +--- + +import { Aside } from "@components" + +A contract is ACE-compatible when it can route function calls through a PolicyEngine for compliance checks before execution. This requires four things: inheriting a base contract, marking functions for protection, connecting to a PolicyEngine, and registering extractors. This page explains what each requirement means at a high level — the sub-pages linked below walk through the implementation in detail. + +For background on how these components interact, see the [Architecture page](/ace/concepts/architecture#policy-management-contracts) and the [Policy Management](/ace/concepts/policy-management) concepts page. + + + +## What your contract needs + +### 1. Inherit from PolicyProtected + +Your contract must inherit from `PolicyProtected` (for new contracts) or `PolicyProtectedUpgradeable` (for contracts deployed behind a proxy that need an upgrade path). This base contract provides: + +- The `runPolicy` and `runPolicyWithContext` modifiers that hook your functions into the policy system. +- Functions to attach and manage the connection to a PolicyEngine. +- Context handling for passing additional data (like offchain signatures) to policies. + +### 2. Add the runPolicy modifier to protected functions + +Any function that should be subject to compliance checks needs the `runPolicy` modifier. The modifier intercepts the call and routes it through the [PolicyEngine](/ace/guides/policy-manager/manage-engines) before your function body executes. + +```solidity +// Before: no compliance checks +function transfer(address to, uint256 amount) public returns (bool) { + return super.transfer(to, amount); +} + +// After: the PolicyEngine checks all attached policies before execution +function transfer(address to, uint256 amount) public runPolicy returns (bool) { + return super.transfer(to, amount); +} +``` + +You choose which functions to protect. Unprotected functions continue to work normally without any policy checks. + +### 3. Connect to a PolicyEngine + +Your contract must be connected to a [PolicyEngine](/ace/guides/policy-manager/manage-engines) — the central orchestrator that holds all policies and executes them in order when a protected function is called. The connection is established during initialization (for [new contracts](/ace/guides/policy-manager/contracts/new-contract)) or migration (for [upgrades](/ace/guides/policy-manager/contracts/upgrade-existing)). + +### 4. Register extractors for protected functions + +[Extractors](/ace/guides/policy-manager/manage-policies#managing-extractors) are helper contracts that parse the calldata of your protected functions into named parameters (for example, `to` and `value` for an ERC-20 `transfer`). Policies use these named parameters to make their decisions — a volume limit policy reads `value`, a sanctions check reads `to`. + +One extractor is registered per function signature. To bind policies to specific functions, see [Protecting Target Functions](/ace/guides/policy-manager/manage-protections). + + + +## Integration paths + +How you integrate ACE depends on where your contract is today: + +- **[Building a New Contract](/ace/guides/policy-manager/contracts/new-contract)** — Starting a new project? ACE provides audited reference implementations for ERC-20 and ERC-3643 tokens that come pre-integrated with PolicyProtected. This is the fastest path. + +- **[Upgrading Existing Contracts](/ace/guides/policy-manager/contracts/upgrade-existing)** — Already have a deployed contract behind a proxy? You can add ACE compliance through a standard proxy upgrade without disrupting existing state, balances, or integrations. + +- **Non-upgradeable contract?** — If your contract is not behind a proxy, the upgrade guide also covers [alternative approaches](/ace/guides/policy-manager/contracts/upgrade-existing#alternatives-for-non-upgradeable-contracts) — wrapped contracts, contract migration, and edge protection — each with different tradeoffs depending on your constraints. diff --git a/src/content/ace/guides/policy-manager/contracts/erc20-token.mdx b/src/content/ace/guides/policy-manager/contracts/erc20-token.mdx new file mode 100644 index 00000000000..74722c36b0d --- /dev/null +++ b/src/content/ace/guides/policy-manager/contracts/erc20-token.mdx @@ -0,0 +1,102 @@ +--- +section: ace +title: "Building an ERC-20 Compliance Token" +date: Last Modified +metadata: + description: "Build and deploy a policy-protected ERC-20 token using the Chainlink ACE reference implementation: token features, frozen token behavior, deployment patterns, and policy setup." + datePublished: "2026-03-31" + lastModified: "2026-03-31" +--- + +import { Aside } from "@components" + +The `ComplianceTokenERC20` is a ready-to-deploy, policy-protected ERC-20 token provided as an ACE reference implementation. It inherits `PolicyProtectedUpgradeable`, routes every state-changing function through a PolicyEngine, and is designed for deployment behind a proxy. + +For a comparison with the ERC-3643 variant and guidance on which to choose, see [Building a New Contract](/ace/guides/policy-manager/contracts/new-contract#choosing-between-erc-20-and-erc-3643). + +## What makes it ACE-compatible + +The token satisfies all the requirements described in [Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible): + +1. **Inherits `PolicyProtectedUpgradeable`** — The contract calls `__PolicyProtected_init` during initialization, which sets the contract owner and connects it to a PolicyEngine. +1. **All state-changing functions are policy-protected** — Every function that modifies balances, allowances, or frozen state carries the `runPolicy` or `runPolicyWithContext` modifier. The PolicyEngine evaluates all attached policies before the function body executes. +1. **ERC-7201 namespaced storage** — All token state lives in a dedicated `ComplianceTokenStoreERC20` storage struct, following the [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) pattern for safe upgradeable storage. + +## Protected functions + +Every state-changing function on the token is policy-protected. The [`runPolicy` modifier](/ace/concepts/policy-management#the-policy-execution-flow) intercepts each call and routes it through the PolicyEngine, which evaluates all attached policies before the function body executes. Functions that need to pass additional context (such as offchain signatures or metadata) use [`runPolicyWithContext`](/ace/concepts/policy-management#the-context-parameter) instead, which forwards a `bytes context` parameter to every policy in the chain. + +### ERC-20 standard + +| Function |
Modifier
| Description | +| -------------------------------- | ----------------------------------------- | ---------------------------------------------------------------- | +| `transfer(to, amount)` | `runPolicy` | Transfer tokens from the caller to another address. | +| `transferFrom(from, to, amount)` | `runPolicy` | Transfer tokens on behalf of another address using an allowance. | +| `approve(spender, amount)` | `runPolicy` | Set an allowance for a spender. | + +### Minting and burning + +| Function | Modifier | Description | +| ------------------------ | ----------- | ------------------------------------------------ | +| `mint(to, amount)` | `runPolicy` | Create new tokens and assign them to an address. | +| `burn(amount)` | `runPolicy` | Destroy tokens from the caller's balance. | +| `burnFrom(from, amount)` | `runPolicy` | Destroy tokens from another address. | + +### Administrative and compliance + +| Function |
Modifier
| Description | +| ------------------------------------------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | +| `freeze(account, amount, context)` | `runPolicyWithContext` | Freeze a specific amount of tokens on an account. Frozen tokens cannot be transferred or burned. | +| `unfreeze(account, amount, context)` | `runPolicyWithContext` | Unfreeze a previously frozen amount on an account. | +| `forceTransfer(from, to, amount, context)` | `runPolicyWithContext` | Administratively move tokens between accounts, subject to frozen balance checks. | + + + +## Frozen token behavior + +`ComplianceTokenERC20` uses a **strict preservation** model for frozen tokens: + +- **Available balance** = total balance - frozen balance. Every transfer, burn, and force transfer checks that the sender has sufficient _unfrozen_ balance and reverts if not. +- **No automatic unfreezing** — Frozen tokens remain frozen during all operations. An administrator must explicitly call `unfreeze` before those tokens can be moved or burned. +- **Pre-freezing** — Tokens can be frozen on an account before they are received. The frozen amount is tracked independently from the balance, so an admin can set a frozen amount in advance and the restriction takes effect as soon as tokens arrive. + +This model provides maximum compliance control: every change to frozen status is an explicit, auditable administrative action. + + + +## Storage layout + +All token state is stored in `ComplianceTokenStoreERC20`, which uses ERC-7201 namespaced storage at a deterministic slot: + +| Field | Type | Description | +| ---------------- | ------------------------------------------------- | --------------------------------- | +| `name` | `string` | Token name. | +| `symbol` | `string` | Token symbol. | +| `decimals` | `uint8` | Decimal precision for display. | +| `totalSupply` | `uint256` | Total supply of tokens. | +| `balances` | `mapping(address => uint256)` | Per-account token balances. | +| `allowances` | `mapping(address => mapping(address => uint256))` | Per-account spender allowances. | +| `frozenBalances` | `mapping(address => uint256)` | Per-account frozen token amounts. | +| `data` | `mapping(bytes32 => bytes)` | Generic storage for extensions. | + +## CCIP compatibility + +The contract exposes `getCCIPAdmin()`, which returns the contract owner. This enables integration with [Chainlink CCIP](/ccip) for cross-chain token transfers by identifying the admin authorized to configure the token's CCIP settings. + +## Reference implementation + +The full source code for the ERC-20 compliance token: + +- [ComplianceTokenERC20.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/tokens/erc-20/src/ComplianceTokenERC20.sol) — Token contract with all protected functions and frozen token logic. +- [ComplianceTokenStoreERC20.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/tokens/erc-20/src/ComplianceTokenStoreERC20.sol) — ERC-7201 namespaced storage layout. diff --git a/src/content/ace/guides/policy-manager/contracts/erc3643-token.mdx b/src/content/ace/guides/policy-manager/contracts/erc3643-token.mdx new file mode 100644 index 00000000000..8ae2d8e43eb --- /dev/null +++ b/src/content/ace/guides/policy-manager/contracts/erc3643-token.mdx @@ -0,0 +1,160 @@ +--- +section: ace +title: "Building an ERC-3643 Compliance Token" +date: Last Modified +metadata: + description: "Build and deploy an ERC-3643 (T-REX) compliant token using Chainlink ACE's Cross-Chain Identity instead of ONCHAINID: key differences, frozen token behavior, and when to choose ERC-3643." + datePublished: "2026-03-31" + lastModified: "2026-03-31" +--- + +import { Aside } from "@components" + +The `ComplianceTokenERC3643` implements the [ERC-3643 (T-REX)](https://eips.ethereum.org/EIPS/eip-3643) `IToken` interface but replaces the canonical T-REX identity and compliance systems with ACE equivalents. It inherits `PolicyProtectedUpgradeable`, is deployed behind a proxy, and routes all state-changing functions through a PolicyEngine. + +For a comparison with the ERC-20 variant and guidance on which to choose, see [Building a New Contract](/ace/guides/policy-manager/contracts/new-contract#choosing-between-erc-20-and-erc-3643). + +## What makes it ACE-compatible + +The token satisfies all the requirements described in [Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible): + +1. **Inherits `PolicyProtectedUpgradeable`** — The contract calls `__PolicyProtected_init` during initialization, which sets the contract owner and connects it to a PolicyEngine. +1. **All state-changing functions are policy-protected** — Every function that modifies state carries the `runPolicy` modifier. The PolicyEngine evaluates all attached policies before the function body executes. +1. **ERC-7201 namespaced storage** — All token state lives in a dedicated `ComplianceTokenStoreERC3643` storage struct, following the [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) pattern for safe upgradeable storage. + +## How it differs from canonical T-REX + +This implementation keeps the `IToken` interface that T-REX tooling and auditors expect, but swaps out the two internal subsystems for ACE equivalents: + +### Identity: ACE Cross-Chain Identity replaces ONCHAINID + +The canonical T-REX stack uses ONCHAINID for on-chain identity claims. This implementation replaces it with ACE's [Cross-Chain Identity](/ace/concepts/cross-chain-identity) infrastructure (IdentityRegistry and CredentialRegistry). The legacy interface stubs remain to satisfy `IToken` but are not functional: + +- `identityRegistry()` returns `address(0)`. +- `onchainID()` returns `address(0)`. +- `setIdentityRegistry()` reverts with "Not implemented". +- `setOnchainID()` reverts with "Not implemented". + +Identity verification is handled through ACE policies that validate credentials against the IdentityRegistry and CredentialRegistry. + +### Compliance: ACE Policy Management replaces ModularCompliance + +The canonical T-REX stack uses `ModularCompliance` for transfer rules. This implementation replaces it with ACE's [Policy Management](/ace/concepts/policy-management) system, where compliance rules are defined as policies attached to the PolicyEngine. The legacy stub remains: + +- `compliance()` returns `address(0)`. +- `setCompliance()` reverts with "Not implemented". + +### Wallet recovery not implemented + +- `recoveryAddress()` reverts with "Not implemented". Wallet recovery is not supported in this implementation. + +## Protected functions + +Every state-changing function on the token is policy-protected with [`runPolicy`](/ace/concepts/policy-management#the-policy-execution-flow), which intercepts each call and routes it through the PolicyEngine. The engine evaluates all attached policies before the function body executes. Functions that interact with user balances also carry the `whenNotPaused` modifier, which checks the token's pause state before proceeding. + +### Transfers + +| Function | Modifiers | Description | +| ---------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `transfer(to, amount)` | `whenNotPaused`, `runPolicy` | Transfer tokens from the caller to another address. Checks that neither wallet is frozen and that the sender has sufficient unfrozen balance. | +| `transferFrom(from, to, amount)` | `whenNotPaused`, `runPolicy` | Transfer tokens on behalf of another address using an allowance. Same frozen and balance checks as `transfer`. | +| `forcedTransfer(from, to, amount)` | `runPolicy` | Administrative transfer that auto-unfreezes tokens if the unfrozen balance is insufficient. | + +### Allowances + +| Function | Modifiers | Description | +| --------------------------------------------- | ---------------------------- | ------------------------------- | +| `approve(spender, amount)` | `whenNotPaused`, `runPolicy` | Set an allowance for a spender. | +| `increaseAllowance(spender, addedValue)` | `whenNotPaused`, `runPolicy` | Increase an existing allowance. | +| `decreaseAllowance(spender, subtractedValue)` | `whenNotPaused`, `runPolicy` | Decrease an existing allowance. | + +### Minting and burning + +| Function |
Modifiers
| Description | +| --------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------- | +| `mint(to, amount)` | `runPolicy` | Create new tokens and assign them to an address. | +| `burn(userAddress, amount)` | `runPolicy` | Destroy tokens from an address. Auto-unfreezes tokens if the unfrozen balance is insufficient. | + +### Freezing + +| Function |
Modifiers
| Description | +| -------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | +| `setAddressFrozen(userAddress, freeze)` | `runPolicy` | Freeze or unfreeze an entire address. A frozen address cannot send or receive tokens through regular transfers. | +| `freezePartialTokens(userAddress, amount)` | `runPolicy` | Freeze a specific amount of tokens on an account. | +| `unfreezePartialTokens(userAddress, amount)` | `runPolicy` | Unfreeze a previously frozen amount on an account. | + +### Token administration + +| Function | Modifiers | Description | +| ------------------- | ----------- | ---------------------------------------------------------------- | +| `pause()` | `runPolicy` | Pause the token. All functions with `whenNotPaused` will revert. | +| `unpause()` | `runPolicy` | Unpause the token. | +| `setName(name)` | `runPolicy` | Update the token name. | +| `setSymbol(symbol)` | `runPolicy` | Update the token symbol. | + +## Frozen token behavior + +`ComplianceTokenERC3643` uses an **automatic unfreezing** model, following the standard T-REX approach. There are two independent freeze mechanisms: + +- **Address freeze** — A boolean flag (`frozen[address]`) that blocks an address from sending or receiving tokens through regular `transfer` and `transferFrom` calls. +- **Partial token freeze** — A numeric amount (`frozenTokens[address]`) that restricts how many of an account's tokens can be moved. Available balance = total balance - frozen tokens. + +Regular transfers check both: the wallet must not be address-frozen, and the transfer amount must not exceed the unfrozen balance. + +**Administrative operations auto-unfreeze.** When `forcedTransfer` or `burn` is called and the unfrozen balance is insufficient, the contract automatically reduces `frozenTokens` by the shortfall and emits a `TokensUnfrozen` event. This means administrative actions are never blocked by partial frozen status — the admin has already decided the operation is necessary. + + + +## Built-in pause + +The token includes a built-in `pause`/`unpause` mechanism. Both functions are policy-protected. When paused, all functions carrying the `whenNotPaused` modifier revert — this includes `transfer`, `transferFrom`, `approve`, `increaseAllowance`, and `decreaseAllowance`. + +Administrative functions (`mint`, `burn`, `forcedTransfer`, freeze operations) do **not** carry `whenNotPaused` and remain callable while the token is paused. + + + +## Batch operations + +The ERC-3643 token supports batch operations for managing large numbers of holders efficiently: + +- `batchTransfer` — Transfer to multiple recipients in a single transaction. +- `batchForcedTransfer` — Force-transfer between multiple address pairs. +- `batchMint` — Mint to multiple recipients. +- `batchBurn` — Burn from multiple addresses. +- `batchSetAddressFrozen` — Freeze or unfreeze multiple addresses. +- `batchFreezePartialTokens` — Freeze token amounts on multiple accounts. +- `batchUnfreezePartialTokens` — Unfreeze token amounts on multiple accounts. + +Each batch function delegates to its single-item counterpart in a loop, so every individual operation goes through `runPolicy` independently. + +## Storage layout + +All token state is stored in `ComplianceTokenStoreERC3643`, which uses ERC-7201 namespaced storage at a deterministic slot: + +| Field | Type | Description | +| --------------- | ------------------------------------------------- | --------------------------------- | +| `tokenName` | `string` | Token name. | +| `tokenSymbol` | `string` | Token symbol. | +| `tokenDecimals` | `uint8` | Decimal precision for display. | +| `tokenPaused` | `bool` | Whether the token is paused. | +| `totalSupply` | `uint256` | Total supply of tokens. | +| `balances` | `mapping(address => uint256)` | Per-account token balances. | +| `allowances` | `mapping(address => mapping(address => uint256))` | Per-account spender allowances. | +| `frozen` | `mapping(address => bool)` | Per-account address freeze flag. | +| `frozenTokens` | `mapping(address => uint256)` | Per-account frozen token amounts. | + +## Reference implementation + +The full source code for the ERC-3643 compliance token: + +- [ComplianceTokenERC3643.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/tokens/erc-3643/src/ComplianceTokenERC3643.sol) — Token contract implementing the `IToken` interface with ACE policy protection. +- [ComplianceTokenStoreERC3643.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/tokens/erc-3643/src/ComplianceTokenStoreERC3643.sol) — ERC-7201 namespaced storage layout. diff --git a/src/content/ace/guides/policy-manager/contracts/new-contract.mdx b/src/content/ace/guides/policy-manager/contracts/new-contract.mdx new file mode 100644 index 00000000000..110ecdb1f3d --- /dev/null +++ b/src/content/ace/guides/policy-manager/contracts/new-contract.mdx @@ -0,0 +1,76 @@ +--- +section: ace +title: "Building a New Contract" +date: Last Modified +metadata: + description: "Choose the right ACE-compatible contract type for your use case: ERC-20 compliance token or ERC-3643 (T-REX) compliant token, and understand the differences between them." + datePublished: "2026-03-31" + lastModified: "2026-03-31" +--- + +import { Aside } from "@components" + +If you are starting a new project, ACE provides audited reference implementations that come pre-integrated with PolicyProtected. You do not need to implement the ACE integration yourself — these contracts are ready to deploy and protect with policies. + +If you already have a deployed contract, see [Upgrading Existing Contracts](/ace/guides/policy-manager/contracts/upgrade-existing) instead. + +## Reference implementations + +ACE offers two token implementations, each designed for different regulatory contexts: + +- **ComplianceTokenERC20** — A policy-protected ERC-20 token with advanced frozen token handling, force transfers, and mint/burn controls. +- **ComplianceTokenERC3643** — A compliant implementation of the [ERC-3643 (T-REX)](https://eips.ethereum.org/EIPS/eip-3643) standard, using ACE Cross-Chain Identity instead of ONCHAINID and ACE Policy Management instead of T-REX ModularCompliance. + +Both implementations inherit from `PolicyProtectedUpgradeable` and must be deployed behind a proxy. During ACE Beta, deployment is managed through the ACE Platform. + +## Choosing between ERC-20 and ERC-3643 + +The right choice depends on your regulatory requirements, the asset type you are tokenizing, and how you need frozen tokens to behave. + +| Aspect | ERC-20 Compliance Token | ERC-3643 Compliance Token | +| ------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| **Standard** | ERC-20 | ERC-3643 (T-REX) | +| **Regulatory context** | Broad — suitable for any token that needs policy-based compliance | Securities — designed for regulated securities and financial instruments | +| **Identity system** | ACE Cross-Chain Identity | ACE Cross-Chain Identity (replaces ONCHAINID) | +| **Compliance system** | ACE Policy Management | ACE Policy Management (replaces T-REX ModularCompliance) | +| **Frozen token behavior** | Strict preservation — frozen tokens remain frozen during burns and force transfers | Operational flexibility — burns and force transfers can automatically unfreeze tokens when needed | +| **Pause support** | No built-in pause (use a PausePolicy instead) | Built-in `pause`/`unpause` with `whenNotPaused` modifier | +| **Batch operations** | No | Yes — batch transfer, mint, burn, freeze/unfreeze | +| **CCIP admin** | `getCCIPAdmin()` returns the contract owner | Not included | + +### Frozen token behavior explained + +The most significant difference between the two implementations is how frozen tokens are handled during administrative operations: + +**ERC-20 approach (strict preservation):** When an admin performs a burn or force transfer on an account with frozen tokens, the frozen balance is preserved. The operation only succeeds if the account has sufficient _unfrozen_ balance. This means an admin must explicitly unfreeze tokens before they can be burned or force-transferred. + +**ERC-3643 approach (automatic unfreezing):** When an admin performs a burn or force transfer, the contract automatically unfreezes tokens if the unfrozen balance is insufficient. This follows the T-REX philosophy that administrative actions should not be blocked by frozen status — the admin has already decided the operation is necessary. + +### When to choose each + +**Choose ERC-20** when: + +- You need a general-purpose compliant token without a specific regulatory framework requirement. +- You want strict control over frozen tokens — every unfreeze must be an explicit administrative action. +- You plan to integrate with CCIP for cross-chain transfers. + +**Choose ERC-3643** when: + +- You are tokenizing regulated securities and need compliance with the ERC-3643 standard. +- Your regulatory framework requires or benefits from the T-REX interface (existing tooling, auditor familiarity). +- You need batch operations for managing large numbers of holders efficiently. +- You prefer operational flexibility for administrative actions on frozen tokens. + +## Next steps + +- **[Building an ERC-20 Compliance Token](/ace/guides/policy-manager/contracts/erc20-token)** — Detailed guide for deploying and configuring the ERC-20 reference implementation. +- **[Building an ERC-3643 Compliance Token](/ace/guides/policy-manager/contracts/erc3643-token)** — Detailed guide for deploying and configuring the ERC-3643 reference implementation. + + diff --git a/src/content/ace/guides/policy-manager/contracts/security-considerations.mdx b/src/content/ace/guides/policy-manager/contracts/security-considerations.mdx new file mode 100644 index 00000000000..32a89ebde0e --- /dev/null +++ b/src/content/ace/guides/policy-manager/contracts/security-considerations.mdx @@ -0,0 +1,64 @@ +--- +section: ace +title: "Security Considerations" +date: Last Modified +metadata: + description: "Security considerations for ACE smart contract integration: trust boundaries for policies and extractors, context handling pitfalls, external call risks, and non-reverting view function requirements." + datePublished: "2026-03-31" + lastModified: "2026-03-31" +--- + +This page covers the implementation-level security concerns that developers should understand when integrating with ACE contracts — trust boundaries, external call risks, and defensive programming patterns. + +For governance-level security (administration controls, execution ordering, registry governance, privacy guarantees), see the [Security Model](/ace/concepts/security) concepts page. + +## Trust model for policies and extractors + +The PolicyEngine delegates trust to the individual Policy, Extractor, and Mapper contracts it is configured to use. A vulnerability in any one of these components can compromise the entire system. + +### Policy trust + +A malicious or poorly written policy can introduce vulnerabilities at two levels: + +- **The `run()` function** (read-only) — A policy that always returns Allow would bypass all subsequent policies. A policy that makes dangerous external calls could be exploited for denial of service. +- **The `postRun()` function** (state-changing) — This function executes after a successful check and can modify onchain state. A malicious postRun could drain funds, change ownership, or corrupt state. + +Only install trusted, audited policies. ACE provides a library of pre-built, audited policies for common use cases. + +### Extractor trust + +The PolicyEngine relies on Extractors to correctly and honestly parse transaction calldata. If an Extractor is compromised, it could misrepresent the data that policies use for their decisions. For example, an Extractor could report a false `value` for a transfer, causing a VolumePolicy to undercount and allow transactions that should be blocked. + +### External call risks + +Many policies make external calls during execution — for example, querying a credential registry or checking an external data source. Since most policy `run()` functions are `view` (read-only), traditional reentrancy attacks are not possible. However, other risks apply: + +- **Denial of service** — A malicious external contract could revert or consume excessive gas, causing the entire policy chain to fail. +- **Inconsistent reads** — External contract state could change between multiple calls within the same transaction. +- **Gas exhaustion** — Deep call chains across multiple policies with external calls could exceed gas limits. + +For policies with state-changing functions (like `postRun()`), traditional reentrancy protections should be considered if those functions make external calls. + +**Mitigation:** Only interact with well-established, audited external contracts in policy logic. Implement proper error handling so that policies gracefully handle external contract failures rather than cascading reverts. + +## Context handling and race conditions + +The [context parameter](/ace/concepts/policy-management#the-context-parameter) is a powerful feature for passing arbitrary data to policies, but it requires careful handling. + +When using the two-step method (calling `setContext` followed by the protected function), the context is stored per sender in the PolicyProtected contract. If context is set but not consumed in the same atomic transaction, stale context from a previous call could be reused. In contracts used by multiple senders (like relayers or governance contracts), one user's context could potentially be overwritten by another before it is consumed. + +**Mitigation:** Always set and consume context within the same atomic transaction. For contracts with multiple concurrent users, prefer the direct argument method (`runPolicyWithContext`) over the two-step approach. + +## Non-reverting view functions + +All validator functions in the Cross-Chain Identity system — `validate()`, `validateCredentialData()`, and related view functions — must **never revert** under any circumstances. They must always return a boolean result. + +This is a critical reliability requirement. If a validator reverted during a policy check (for example, because an external call to a credential registry failed), it would break the entire policy chain for that transaction. The PolicyEngine would not be able to distinguish between "credential is invalid" and "validator is broken." + +Implementations must use defensive programming patterns: + +- Wrap external calls in try-catch blocks. +- Return `false` on any external call failure rather than allowing the revert to propagate. +- Validate all inputs before making external calls. + +This guarantees that the policy chain always completes and returns a definitive result, even when downstream dependencies fail. diff --git a/src/content/ace/guides/policy-manager/contracts/upgrade-existing.mdx b/src/content/ace/guides/policy-manager/contracts/upgrade-existing.mdx new file mode 100644 index 00000000000..f3be379d2fc --- /dev/null +++ b/src/content/ace/guides/policy-manager/contracts/upgrade-existing.mdx @@ -0,0 +1,535 @@ +--- +section: ace +title: "Upgrading Existing Contracts" +date: Last Modified +metadata: + description: "Add Chainlink ACE compliance to an already-deployed smart contract: storage safety with ERC-7201, two integration approaches, step-by-step instructions, alternatives for non-upgradeable contracts, and FAQ." + datePublished: "2026-03-31" + lastModified: "2026-05-26" +--- + +import { Aside } from "@components" + +This guide explains how to add ACE compliance to a contract that is already deployed. The process is a standard proxy upgrade — your existing state (balances, allowances, mappings) is fully preserved, your contract address stays the same, and all existing integrations continue to work. + + + + + +## Prerequisites + +Before starting, you should be familiar with: + +- [ACE Architecture](/ace/concepts/architecture) — how PolicyEngine, policies, and extractors work together +- [Policy Management](/ace/concepts/policy-management) — the execution model and policy outcomes + +### Your contract must be upgradeable + +This guide covers contracts deployed behind a proxy pattern — UUPS, Transparent Proxy, or Beacon Proxy. You need upgrade authority over the contract. + +If your contract is **not upgradeable**, see [Alternatives for non-upgradeable contracts](#alternatives-for-non-upgradeable-contracts) below. + +## Key concept: Storage safety with ERC-7201 + +When upgrading a contract, new variables must not overwrite existing state. `PolicyProtectedUpgradeable` uses [ERC-7201 namespaced storage](https://eips.ethereum.org/EIPS/eip-7201), which isolates all ACE data in a deterministic storage slot that cannot collide with your existing storage layout. + +```solidity +bytes32 private constant STORAGE_LOCATION = + keccak256(abi.encode(uint256(keccak256("chainlink.ace.PolicyProtected")) - 1)) + & ~bytes32(uint256(0xff)); +``` + +This formula produces a storage location that is guaranteed not to overlap with Solidity's default sequential storage layout. Your existing balances, allowances, and other state remain untouched. + +## Choosing your approach + +There are two ways to integrate ACE into an upgradeable contract: + +| Aspect | Approach 1: Extend PolicyProtectedUpgradeable | Approach 2: Implement IPolicyProtected | +| ------------------------- | --------------------------------------------- | ------------------------------------------- | +| **Bytecode impact** | +5-6 KB | +1-2 KB | +| **Implementation effort** | Add inheritance + modifiers | Write storage, context, and execution logic | +| **Maintenance** | Inherits ACE updates automatically | You maintain all custom code | +| **Risk** | Lower — proven patterns | Higher — custom code means custom bugs | + +**Recommendation:** Use Approach 1 unless your contract is near the 24 KB bytecode limit or you need custom control over how context is stored or policies are executed. + +## Approach 1: Extend PolicyProtectedUpgradeable (recommended) + +This approach inherits from `PolicyProtectedUpgradeable`, which provides built-in modifiers and automatic storage management. + +### Step 1: Update contract inheritance + +Add `PolicyProtectedUpgradeable` to your inheritance chain. + +**Before:** + +```solidity +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract MyToken is Initializable, ERC20Upgradeable, OwnableUpgradeable { + // ... +} +``` + +**After:** + +```solidity +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {PolicyProtectedUpgradeable} from "@chainlink/policy-management/core/PolicyProtectedUpgradeable.sol"; + +contract MyToken is PolicyProtectedUpgradeable, ERC20Upgradeable, UUPSUpgradeable { + // ... +} +``` + + + +### Step 2: Add a migration function + +Your original `initialize()` has already been called, so you cannot modify it. Instead, add a migration function using `reinitializer`: + +```solidity +function migrateToACE(address policyEngine) public reinitializer(2) onlyOwner { + __PolicyProtected_init_unchained(policyEngine); +} +``` + +`reinitializer(2)` ensures this migration runs exactly once (version 1 was your original `initialize()`). If you have had previous upgrades with reinitializers, increment the version accordingly. + +`__PolicyProtected_init_unchained()` stores the PolicyEngine address in namespaced storage and registers your contract with the PolicyEngine. + + + +If you need to switch to a different PolicyEngine later, call `attachPolicyEngine(newAddress)` (owner-only). + +### Step 3: Add runPolicy to protected functions + +Add the `runPolicy` modifier to each function that should be subject to policy checks. + +**Before:** + +```solidity +function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); +} + +function transfer(address to, uint256 amount) public virtual override returns (bool) { + return super.transfer(to, amount); +} +``` + +**After:** + +```solidity +function mint(address to, uint256 amount) public runPolicy { + _mint(to, amount); +} + +function transfer(address to, uint256 amount) public virtual override runPolicy returns (bool) { + return super.transfer(to, amount); +} +``` + +Access control (restricting who can mint, for example) is now enforced through policies rather than traditional `onlyOwner` modifiers. This lets you change access rules by updating policies without upgrading the contract. + +For functions that need additional data passed to policies (signatures, proofs), use `runPolicyWithContext`: + +```solidity +function forceTransfer( + address from, + address to, + uint256 amount, + bytes calldata context +) public runPolicyWithContext(context) { + _update(from, to, amount); +} +``` + +### Complete before/after example + +**Before (standard upgradeable ERC-20):** + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +contract MyToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable { + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner) public initializer { + __ERC20_init("MyToken", "MTK"); + __Ownable_init(initialOwner); + } + + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} +} +``` + +**After (with ACE integration):** + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {PolicyProtectedUpgradeable} from "@chainlink/policy-management/core/PolicyProtectedUpgradeable.sol"; + +contract MyToken is PolicyProtectedUpgradeable, ERC20Upgradeable, UUPSUpgradeable { + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner) public initializer { + __ERC20_init("MyToken", "MTK"); + __Ownable_init(initialOwner); + } + + function migrateToACE(address policyEngine) public reinitializer(2) onlyOwner { + __PolicyProtected_init_unchained(policyEngine); + } + + function mint(address to, uint256 amount) public runPolicy { + _mint(to, amount); + } + + function transfer(address to, uint256 amount) + public + virtual + override + runPolicy + returns (bool) + { + return super.transfer(to, amount); + } + + function transferFrom(address from, address to, uint256 amount) + public + virtual + override + runPolicy + returns (bool) + { + return super.transferFrom(from, to, amount); + } + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} +} +``` + +**Key changes:** + +1. Import and inherit `PolicyProtectedUpgradeable` (remove explicit `Initializable` and `OwnableUpgradeable` — they are inherited through `PolicyProtectedUpgradeable`). +2. Add `migrateToACE()` with `reinitializer(2)`. +3. Add `runPolicy` to functions that need policy protection. + +## Approach 2: Implement IPolicyProtected (advanced) + +If your contract is near the 24 KB bytecode limit or you need custom control over policy execution, you can implement the `IPolicyProtected` interface directly instead of inheriting from `PolicyProtectedUpgradeable`. This adds only ~1-2 KB of bytecode but requires more code. + +### What you must implement + +You are responsible for: + +1. **Storage** — Storing the PolicyEngine address and per-sender context using ERC-7201 namespaced storage. +2. **Policy execution** — Calling `policyEngine.run()` with the correct payload in each protected function. +3. **Context handling** — Storing, retrieving, and clearing context data. +4. **Registration** — Attaching to and detaching from the PolicyEngine. +5. **ERC-165 support** — Implementing `supportsInterface()`. + +### Interface methods + +```solidity +interface IPolicyProtected { + function attachPolicyEngine(address policyEngine) external; + function getPolicyEngine() external view returns (address); + function setContext(bytes calldata context) external; + function getContext() external view returns (bytes memory); + function clearContext() external; +} +``` + +| Method | Purpose | +| -------------------- | ----------------------------------------------- | +| `attachPolicyEngine` | Registers your contract with a PolicyEngine | +| `getPolicyEngine` | Returns the current PolicyEngine address | +| `setContext` | Stores context data for the next protected call | +| `getContext` | Retrieves stored context for the current caller | +| `clearContext` | Clears context after use to prevent replay | + +### Implementation skeleton + +The following skeleton shows the key pieces for an ERC-20 token. It uses the same migration pattern as Approach 1, but all ACE logic is implemented manually. + +```solidity +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {IPolicyProtected} from "@chainlink/policy-management/interfaces/IPolicyProtected.sol"; +import {IPolicyEngine} from "@chainlink/policy-management/interfaces/IPolicyEngine.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +contract MyToken is ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable, IPolicyProtected { + + // --- ERC-7201 Namespaced Storage --- + + struct ACEStorage { + address policyEngine; + mapping(address => bytes) senderContext; + } + + // Replace with your calculated ERC-7201 storage slot + bytes32 private constant ACE_STORAGE_LOCATION = 0x...; + + function _getACEStorage() private pure returns (ACEStorage storage $) { + assembly { + $.slot := ACE_STORAGE_LOCATION + } + } + + // --- Migration --- + + function migrateToACE(address policyEngine) public reinitializer(2) onlyOwner { + _attachPolicyEngine(policyEngine); + } + + // --- IPolicyProtected --- + + function attachPolicyEngine(address policyEngine) external onlyOwner { + _attachPolicyEngine(policyEngine); + } + + function _attachPolicyEngine(address policyEngine) internal { + require(policyEngine != address(0), "Zero address"); + ACEStorage storage $ = _getACEStorage(); + $.policyEngine = policyEngine; + IPolicyEngine(policyEngine).attach(); + } + + function getPolicyEngine() public view returns (address) { + return _getACEStorage().policyEngine; + } + + function setContext(bytes calldata context) external { + _getACEStorage().senderContext[msg.sender] = context; + } + + function getContext() public view returns (bytes memory) { + return _getACEStorage().senderContext[msg.sender]; + } + + function clearContext() public { + delete _getACEStorage().senderContext[msg.sender]; + } + + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return interfaceId == type(IPolicyProtected).interfaceId || + interfaceId == type(IERC165).interfaceId; + } + + // --- Policy Execution --- + + function _runPolicy() internal { + ACEStorage storage $ = _getACEStorage(); + require($.policyEngine != address(0), "PolicyEngine not set"); + + bytes memory context = getContext(); + IPolicyEngine($.policyEngine).run( + IPolicyEngine.Payload({ + selector: msg.sig, + sender: msg.sender, + data: msg.data[4:], + context: context + }) + ); + + if (context.length > 0) { + clearContext(); + } + } + + // --- Protected Functions --- + + function transfer(address to, uint256 amount) public virtual override returns (bool) { + _runPolicy(); + return super.transfer(to, amount); + } + + // ... other protected functions follow the same pattern +} +``` + + + +## Execute the upgrade + +At this point your updated implementation contract is ready. You need the PolicyEngine address to proceed. + +### Pre-upgrade checklist + +**Development:** + +- Updated contract compiles successfully +- Final bytecode is under 24 KB +- Unit tests pass +- Integration tests with PolicyEngine pass + +**Infrastructure:** + +- PolicyEngine address received from the ACE Platform (Beta) or deployed by your team (GA) + +### Upgrade execution + +Deploy the new implementation, then execute the upgrade and migration in one transaction. The exact pattern depends on your proxy type: + +**UUPS:** + +```solidity +bytes memory data = abi.encodeCall(MyToken.migrateToACE, (policyEngineAddress)); +MyToken(proxyAddress).upgradeToAndCall(newImplementationAddress, data); +``` + +**Transparent Proxy:** + +```solidity +bytes memory data = abi.encodeCall(MyToken.migrateToACE, (policyEngineAddress)); +ProxyAdmin(proxyAdminAddress).upgradeAndCall(proxyAddress, newImplementationAddress, data); +``` + +**Beacon Proxy:** + +```solidity +// Beacon does not support upgradeAndCall — execute separately +UpgradeableBeacon(beaconAddress).upgradeTo(newImplementationAddress); +MyToken(proxyAddress).migrateToACE(policyEngineAddress); +``` + +### Post-upgrade verification + +- `getPolicyEngine()` returns the correct address +- Protected functions trigger policy checks +- Policies allow and reject transactions as expected +- Existing balances, allowances, and other state are unchanged + +## Alternatives for non-upgradeable contracts + +If your contract is not deployed behind a proxy, a standard upgrade is not possible. Depending on your situation, there are three alternative approaches to bring ACE compliance to your application. + +### Wrapped contract + +Deploy a new ACE-compatible wrapper contract that sits in front of your original contract. Users interact with the wrapper, which enforces policies before delegating calls to the underlying contract. + +**How it works:** The wrapper inherits from `PolicyProtected` and exposes the same external interface as the original contract. Each function on the wrapper calls `runPolicy`, then forwards the call to the original contract. The original contract remains completely untouched. + +**When to use:** Your contract's logic does not need to change, but you need compliance checks on interactions with it. Works well for contracts where you can redirect user traffic to a new entry point. + +**Tradeoffs:** + +- The wrapper has a **different contract address**, so integrators (DEXs, lending protocols, front ends) must update their references. +- If wrapping a token, users may need to **migrate balances** or **re-approve allowances** to the wrapper. +- Adds a layer of indirection, which slightly increases gas costs per call. + +### Contract migration + +Deploy a brand-new ACE-native contract and migrate state from the old contract to the new one. The new contract is built from scratch with `PolicyProtected` integrated from the start. + +**How it works:** You take a snapshot of the old contract's state (balances, allowances, roles, etc.) and seed the new contract with that data during deployment or through a claim-based migration. The old contract is then deprecated or paused. + +**When to use:** You want no wrapper indirection, no legacy contract to maintain. Particularly suited for tokens where a coordinated migration event is feasible (for example, a token swap or airdrop). + +**Tradeoffs:** + +- Requires a **coordinated migration event** — all holders and integrators must move to the new contract. +- The new contract has a **different address**, which affects all downstream integrations. +- Migration patterns (snapshot + airdrop, or claim-based redemption) add operational complexity. +- The old contract must be handled (paused, drained, or deprecated) to prevent confusion. + +### Edge protection + +Instead of modifying your contract, apply ACE policies at the integration points that interact with it — for example, a DEX pool, a bridge, or a lending protocol front end. + +**How it works:** The protected contract is not your original contract, but the integration layer. A DEX pool contract or a custom router contract inherits `PolicyProtected` and enforces compliance checks before interacting with your original token or vault. Your contract is never modified. + +**When to use:** Modifying the contract is not an option (immutable deployment, no migration path), and you can control the integration points where compliance matters. Works well when compliance is needed at specific boundaries rather than on every direct interaction. + +**Tradeoffs:** + +- **Does not protect direct contract interactions** — any user who calls your contract directly (bypassing the protected integration point) is not subject to policy checks. +- Only covers the specific integration points where ACE is applied. Comprehensive coverage requires wrapping all relevant entry points. +- The original contract's functionality is unchanged, which may be a regulatory concern if direct access remains open. + + + +## FAQ + +### Will this upgrade overwrite my existing state? + +No. `PolicyProtectedUpgradeable` uses ERC-7201 namespaced storage, which stores ACE data in an isolated slot. Your existing balances, allowances, and all other state remain untouched. + +### What happens to token balances and allowances? + +All state is preserved. The upgrade replaces the implementation contract (the code), but all state lives in the proxy's storage and is not affected. Users do not need to re-approve. + +### What about tokens held in external contracts (DEXs, protocols)? + +Unaffected. Your contract address does not change, so all existing integrations continue working. The only difference is that transactions may revert if policies reject them. + +### Can I protect only some functions? + +Yes. You only add `runPolicy` to the functions you want to protect. All other functions continue working normally without policy checks. + +### Can I update policies after the upgrade? + +Yes. Policies can be added, removed, reordered, and reconfigured through the ACE Platform without touching your contract code. + +### What if I need to switch to a different PolicyEngine? + +Call `attachPolicyEngine(newAddress)` (owner-only). This detaches the old engine and registers your contract with the new one. Once ACE is integrated, a PolicyEngine is always required — you cannot set it to the zero address. + +### How many policies can I attach to a single function? + +The PolicyEngine supports up to 8 policies per function selector. + +### My contract is near the 24 KB bytecode limit. What can I do? + +Use [Approach 2](#approach-2-implement-ipolicyprotected-advanced), which adds only ~1-2 KB. You can also enable the Solidity optimizer with higher runs, move logic to external libraries, or split functionality into separate contracts. diff --git a/src/content/ace/guides/policy-manager/custom-policies.mdx b/src/content/ace/guides/policy-manager/custom-policies.mdx new file mode 100644 index 00000000000..9748a3821b4 --- /dev/null +++ b/src/content/ace/guides/policy-manager/custom-policies.mdx @@ -0,0 +1,235 @@ +--- +section: ace +title: "Custom Policies" +date: Last Modified +metadata: + description: "Write, deploy, and register your own ACE policy contract, then use it like any pre-built policy: the IPolicy contract, the config schema, registering an implementation, and creating instances." + datePublished: "2026-07-17" + lastModified: "2026-07-17" +--- + +import { Aside } from "@components" + +In addition to the [pre-built Policy Library](/ace/reference/policy-library), you can write and deploy **your own policy contract** and register it with the ACE Platform. Once registered, a custom policy behaves exactly like a library policy — you create instances of it, configure them, and attach them to protected functions. + +A custom policy implementation is **private to your organization**: it appears in your Policy Manager alongside the global library, but other organizations do not see it. + + + +## How it fits together + +A custom policy follows the same [implementation vs. instance](/ace/guides/policy-manager/manage-policies#policy-implementations-vs-policy-instances) model as library policies: + +1. **Write** a policy contract that implements the `IPolicy` interface. +1. **Deploy** it — this is your policy **implementation** contract — on each chain where you need it. +1. **Register** the implementation with the ACE Platform, providing its on-chain addresses and a **config schema**. This makes it an org-scoped policy type. +1. **Create instances** from it and **attach** them to target functions, exactly like a library policy. + +At instance-creation time, ACE's on-chain `PolicyFactory` clones your implementation into an instance and initializes it. The factory verifies that your implementation declares support for `IPolicy` (via ERC-165) — a contract that does not implement `IPolicy` cannot be instantiated. + +## Prerequisites + +- Solidity development experience and a deployment toolchain (Foundry, Hardhat, etc.). +- Familiarity with [Policy Management](/ace/concepts/policy-management) (the execution model, `run`/`postRun`, extractors, and parameters) and [Policy Ordering & Composition](/ace/concepts/policy-ordering). +- A deployed [PolicyEngine](/ace/guides/policy-manager/manage-engines). +- The `@chainlink/policy-management` contracts available in your project. + +## Step 1: Write the policy contract + +Every policy inherits from the base `Policy` contract and implements `run`. The base contract provides ownership, upgradeability, ERC-165 support, and the binding to a PolicyEngine. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Policy} from "@chainlink/policy-management/core/Policy.sol"; +import {IPolicyEngine} from "@chainlink/policy-management/interfaces/IPolicyEngine.sol"; + +contract LockoutPolicy is Policy { + string public constant override typeAndVersion = "LockoutPolicy 1.0.0"; + + mapping(address => uint256) public lockoutExpiresAt; + + /// @notice Configuration setter — locks an address for a duration (seconds). + function setLockout(address account, uint256 duration) public onlyOwner { + lockoutExpiresAt[account] = block.timestamp + duration; + } + + /// @notice Authorize setLockout so the PolicyEngine can apply configuration changes. + function authorizeConfigSelector(bytes4 selector) public pure override returns (bool) { + return selector == this.setLockout.selector; + } + + function run( + address, /* caller */ + address, /* subject */ + bytes4, /* selector */ + bytes[] calldata parameters, + bytes calldata /* context */ + ) public view override returns (IPolicyEngine.PolicyResult) { + // Always validate the inputs your policy expects. + require(parameters.length == 1, "LockoutPolicy: expected 1 parameter"); + address recipient = abi.decode(parameters[0], (address)); + + if (lockoutExpiresAt[recipient] > block.timestamp) { + revert IPolicyEngine.PolicyRejected("LockoutPolicy: address is locked out"); + } + return IPolicyEngine.PolicyResult.Continue; + } +} +``` + +Key pieces: + +- **`run(...)`** — read-only evaluation returning `Continue` (defer to the next policy), `Allowed` (approve and skip the rest of the chain), or reverting with `PolicyRejected` to block the transaction. The `parameters` array holds the extractor outputs mapped to this policy; always validate its length and decode defensively. +- **`postRun(...)`** _(optional)_ — override it to mutate state after a successful check (for example, incrementing a counter). It is `onlyPolicyEngine` and is not called when the policy rejects. +- **`configure(bytes)`** _(optional)_ — override it to decode initial configuration passed at instance creation. The base `initialize` calls it. +- **Configuration setters + `authorizeConfigSelector`** — expose owner-callable setters (like `setLockout`) to reconfigure the policy after deployment, and override `authorizeConfigSelector` to return `true` for those selectors so the PolicyEngine is allowed to call them. Selectors you do not authorize can only be called by the owner directly, not through the platform. +- **`typeAndVersion`** — a human-readable identifier, e.g. `"LockoutPolicy 1.0.0"`. + + + +## Step 2: Deploy the implementation + +Deploy your policy contract on each chain where you intend to use it. This deployed contract is the **implementation** — ACE clones it into instances; you do not attach the implementation to functions directly. Record the deployed address per chain; you need them in the next step. + + + +## Step 3: Register the implementation + +Register the deployed implementation with the Coordinator API so the platform can manage it. Provide a name, description, the on-chain addresses, and a **config schema**. + +```bash +curl -X POST https://ace.api.chain.link/v1/policy-implementations \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "Lockout Policy", + "description": "Blocks transfers to locked-out recipients for a period of time", + "onchain_policy_implementations": [ + { "chain_selector": "16015286601757825753", "address": "0xYourImplementationOnSepolia" } + ], + "policy_config_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "lockouts": { + "type": "array", + "description": "Accounts to lock out and for how long.", + "items": { + "type": "object", + "required": ["account", "duration"], + "properties": { + "account": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" }, + "duration": { "type": "integer" } + } + }, + "metadata": { + "display_hints": { "network_behaviour": "apply_per_chain", "title": "Lockouts" }, + "primary_key_fields": ["account"], + "on_chain_operations": [ + { + "type": "add", + "function_abi": { + "name": "setLockout", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { "name": "account", "type": "address" }, + { "name": "duration", "type": "uint256" } + ], + "outputs": [] + } + } + ] + } + } + }, + "policy_run_parameters": [ + { "name": "Recipient", "type": "address", "max": 1 } + ], + "initial_configs": [] + } + }' +``` + +|
Field
| Required | Description | +| -------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `name` | Yes | Human-readable name shown in Policy Manager | +| `description` | Yes | What the policy does | +| `policy_config_schema` | Yes | JSON Schema describing configurable fields, the parameters the policy consumes, and how configuration maps to on-chain setters (see below) | +| `onchain_policy_implementations` | No | Array of `{ chain_selector, address }` for your deployed implementation on each chain. ACE records these; it does **not** deploy the implementation for you. | + +The registered implementation is created with type `custom` and scoped to your organization. It now appears in `GET /policy-implementations` alongside the global library. + +## The config schema + +The `policy_config_schema` is a [JSON Schema (draft-07)](https://json-schema.org/draft-07) document with three ACE-specific parts. It drives the Platform UI, validates the configuration you supply, and tells the platform how to translate configuration into on-chain calls. + +### `properties` — configurable fields + +Each property is a configurable field of your policy. Its `metadata.on_chain_operations` map configuration changes to your contract's setter functions: + +- `add` / `remove` — for list-style fields (add or remove an entry), pointing at setters like `setLockout`. +- `replace` — for scalar fields (set a single value), pointing at a setter like `setMax`. + +Each operation carries the `function_abi` of the setter to call. **Those setters must be authorized by your contract's `authorizeConfigSelector`** — otherwise the PolicyEngine cannot call them and configuration changes will fail. + +### `policy_run_parameters` — what the policy consumes + +An ordered array declaring the parameters your `run` function expects, each with a `name`, a Solidity `type`, and a `max`: + +- `max: 1` — exactly one value at that position. +- `max: -1` — a variable number of values (must be the last parameter). Use this for policies that check an arbitrary number of addresses. + +When you attach the policy to a function, the extractor outputs you map to it must match these parameters by type and position. See [Policy Management — the extractor and mapper pattern](/ace/concepts/policy-management#the-extractor-and-mapper-pattern). + +### `initial_configs` — what is set at creation + +An array of property names that are provided when an instance is **created** (in the instance's `initial_config`) rather than configured afterward. Leave it empty to configure everything after deployment. + +## Step 4: Create and use instances + +From here, a custom policy is used exactly like a library policy: + +1. [Create a policy instance](/ace/guides/policy-manager/manage-policies#create-a-policy-instance) from your implementation, supplying an `initial_config` that matches your config schema. ACE clones your implementation through the `PolicyFactory` and initializes the instance. +1. [Attach the instance to a protected function](/ace/guides/policy-manager/manage-protections), mapping the extractor outputs to your `policy_run_parameters`. +1. [Update the configuration](/ace/guides/policy-manager/manage-policies#update-policy-configuration) over time through the authorized config selectors. + +## Manage a custom implementation + +- **Update** name, description, or on-chain addresses with `PUT /policy-implementations/{id}`. +- **Archive** with `PATCH /policy-implementations/{id}` (`{"status":"archived"}`). All instances of the implementation must be archived first. + +## Security considerations + +A custom policy runs inside the policy chain of every function it protects, so a bug or malicious construct affects those transactions. In particular: + +- Keep `run` read-only and defensive — validate `parameters` length and decode carefully. +- Ensure any external calls cannot revert the whole chain unexpectedly; return a decision rather than propagating failures. +- Treat `postRun` state changes with the same care as any state-changing external function (reentrancy, access control). + +See [Security Considerations](/ace/guides/policy-manager/contracts/security-considerations) and the [Security Model](/ace/concepts/security) for the full trust model. + +## Related pages + +- [Custom Policies Tutorial](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/CUSTOM_POLICIES_TUTORIAL.md) — end-to-end contract walkthrough with a boilerplate template +- [Policy Management](/ace/concepts/policy-management) — execution model, `run`/`postRun`, parameters +- [Policy Management Contracts](/ace/reference/policy-management-contracts) — `IPolicy`, `IPolicyEngine`, and other interfaces +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — create and configure policy instances +- [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) — attach policies to functions +- [Security Considerations](/ace/guides/policy-manager/contracts/security-considerations) — trust boundaries and defensive patterns diff --git a/src/content/ace/guides/policy-manager/index.mdx b/src/content/ace/guides/policy-manager/index.mdx new file mode 100644 index 00000000000..f5e4a259907 --- /dev/null +++ b/src/content/ace/guides/policy-manager/index.mdx @@ -0,0 +1,66 @@ +--- +section: ace +title: "Policy Manager Guides" +isIndex: true +date: Last Modified +metadata: + description: "Overview of the Policy Manager operational model and guides for Chainlink ACE: understand how policy engines, targets, policies, and protections fit together, integrate smart contracts, and configure compliance policies." + datePublished: "2026-04-06" + lastModified: "2026-07-17" +--- + +import { Aside, ClickToZoom } from "@components" + +These guides cover the day-to-day operations of a Policy Manager — from integrating your smart contracts with ACE to configuring and managing compliance policies. + + + +## How it all fits together + +The Policy Manager revolves around a handful of entities that work together to enforce compliance on your smart contracts. Understanding how they relate to each other makes the individual guides much easier to follow. + + + +- **PolicyEngine** — The on-chain orchestrator that evaluates policies. Everything — targets, policy instances, and extractors — is scoped to a single engine. +- **Extractors** — Modules that decode transaction calldata into named parameters (sender, amount, etc.) so policies can evaluate them. Attached to the engine at creation time. +- **Target** — A smart contract registered under an engine via the API or UI. +- **Policy Implementation** — A reusable compliance rule from the [Policy Library](/ace/reference/policy-library) (e.g., allowlist, volume limit, pause toggle). +- **Policy Instance** — A deployed copy of an implementation, configured with your specific parameters and scoped to an engine. +- **Protection** — The binding between a policy instance and a specific function on a target contract. This is what makes a function "policy-protected." +- **Data Validator** — An optional contract attached to an identity policy's credential source that validates the _contents_ of a credential (e.g., a jurisdiction allow/deny list), not just its existence. +- **Managed offchain policy (MVP)** — A CRE workflow and onchain validator managed by Chainlink that evaluate external risk data before issuing a permit for a specific transaction intent. + +### Typical setup flow + +1. [Create a PolicyEngine](/ace/guides/policy-manager/manage-engines) with extractors for your contract type (ERC-20 or ERC-3643). +1. [Integrate your contract](/ace/guides/policy-manager/contracts/ace-compatible) by inheriting `PolicyProtected` and adding the `runPolicy` modifier. +1. Deploy or upgrade your contract — it [appears as a target](/ace/guides/policy-manager/manage-targets) automatically. +1. [Create policy instances](/ace/guides/policy-manager/manage-policies) from the Policy Library with your configuration. +1. [Attach policies to functions](/ace/guides/policy-manager/manage-protections) by creating protections. + +## Smart contract integration + +- [Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible) — what your contract needs to work with ACE (inheriting `PolicyProtected`, adding the `runPolicy` modifier) +- [Building a New ERC-20 Token](/ace/guides/policy-manager/contracts/erc20-token) — reference implementation for a compliance-ready ERC-20 token +- [Building a New ERC-3643 Token](/ace/guides/policy-manager/contracts/erc3643-token) — reference implementation for an ERC-3643 security token +- [Upgrading Existing Contracts](/ace/guides/policy-manager/contracts/upgrade-existing) — how to add ACE compliance to an already-deployed upgradeable contract +- [Security Considerations](/ace/guides/policy-manager/contracts/security-considerations) — key security patterns and pitfalls when integrating with ACE + +## Policy engine and policy management + +- [Managing Policy Engines](/ace/guides/policy-manager/manage-engines) — create, view, update, and archive policy engines +- [Managing Targets](/ace/guides/policy-manager/manage-targets) — register your deployed contracts as targets under a policy engine +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — browse policy implementations, create and configure policy instances +- [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) — bind policy instances to specific functions on your target contracts +- [Managing Data Validators](/ace/guides/policy-manager/manage-data-validators) — enforce rules on credential contents (e.g., jurisdiction allow/deny lists) by attaching Data Validators to identity policies +- [Custom Policies](/ace/guides/policy-manager/custom-policies) — write, deploy, and register your own policy contract, then use it like a library policy + + + +- [Offchain Policies](/ace/guides/policy-manager/offchain-policies) — understand the managed and custom models, configure the managed wallet screening MVP, and integrate offchain permits diff --git a/src/content/ace/guides/policy-manager/manage-data-validators.mdx b/src/content/ace/guides/policy-manager/manage-data-validators.mdx new file mode 100644 index 00000000000..df151e1976d --- /dev/null +++ b/src/content/ace/guides/policy-manager/manage-data-validators.mdx @@ -0,0 +1,197 @@ +--- +section: ace +title: "Managing Data Validators" +date: Last Modified +metadata: + description: "Create, configure, and attach Credential Data Validators in Chainlink ACE to enforce rules on credential contents — such as jurisdiction allow/deny lists — using the Coordinator API." + datePublished: "2026-07-17" + lastModified: "2026-07-17" +--- + +import { Aside } from "@components" + +A **Data Validator** is an on-chain contract that inspects the **contents** of a credential — not just whether it exists. Attaching a Data Validator to an identity-validation policy lets you enforce rules on credential data, such as "only allow investors whose credential says they are in the US or Canada" or "reject any account whose credential country is on a sanctions list". + +This guide covers creating, configuring, and attaching Data Validators. For the credential side of the workflow — linking a data schema to a credential type and issuing credentials with data — see [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types#typed-credentials-with-data-schemas) and [Managing Credentials](/ace/guides/identity-manager/manage-credentials#issue-a-credential-with-data). + + + +## How Data Validators fit in + +Identity-validation policies — the [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) and the [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) — resolve a caller's address to a CCID and check credentials from **credential sources**. Each credential source can optionally reference a Data Validator. + +When a credential source has a Data Validator configured, the policy performs an extra step at transaction time: + +1. Resolve the account's CCID and confirm the credential exists (attestation check). +1. Fetch the credential's stored `credentialData`. +1. Call the Data Validator's `validateCredentialData(...)`, which returns `true` or `false`. + +The credential passes only if **both** the attestation check and the data check succeed. Without a Data Validator, the source is attestation-only — it confirms the credential exists but ignores its contents. + + + +## The AllowDenyList Data Validator + +ACE provides a pre-built, audited Data Validator implementation: the **AllowDenyList Data Validator**. It validates a credential payload against an **allowlist** and a **denylist**, with an optional restriction by credential type. Its rules are: + +- If the **denylist** contains any value present in the credential, validation **fails**. +- If the **allowlist** is non-empty, at least one value in the credential must be allowlisted; otherwise validation **fails**. +- If the allowlist is empty, the allow check passes (deny-only mode). + +The first use case shipped on top of this implementation is **jurisdiction control** using [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes (e.g., `US`, `CA`, `GB`). The country codes are the values checked against the allow and deny lists. + + + +## Prerequisites + +Before creating a Data Validator: + +1. A [policy engine](/ace/guides/policy-manager/manage-engines) deployed on your target chains. +1. A credential type linked to a **data schema** so its credentials carry data — for the jurisdiction use case, the ISO 3166-1 alpha-2 country code schema. See [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types#typed-credentials-with-data-schemas). +1. Credentials issued **with data** against that credential type. See [Managing Credentials](/ace/guides/identity-manager/manage-credentials#issue-a-credential-with-data). + +## Create a Data Validator + +A **Data Validator instance** is a deployed copy of a Data Validator implementation (such as the AllowDenyList country-code validator), configured with your specific allow and deny lists and scoped to one or more chains — the same shape as a policy instance. + +The AllowDenyList (country codes) Data Validator implementation ID is: + +```text +2aed366a-38af-4f48-b8e2-8fd1489db9fa +``` + +Create a Data Validator instance with a `POST` request. Provide the implementation ID and, for each chain, the `initial_config` with your allow and deny lists: + +```bash +curl -X POST https://ace.api.chain.link/v1/data-validators \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "Jurisdiction allow/deny", + "description": "Allow US and CA, deny KP", + "data_validator_implementation_id": "2aed366a-38af-4f48-b8e2-8fd1489db9fa", + "onchain_data_validators": [ + { + "chain_selector": "16015286601757825753", + "initial_config": { + "allowlist": [{ "item": "US" }, { "item": "CA" }], + "denylist": [{ "item": "KP" }], + "supportedDataTypes": [] + } + } + ] + }' +``` + +| Field | Required | Description | +| ---------------------------------- | -------- | ------------------------------------------------------------------------- | +| `name` | Yes | Human-readable name for the instance | +| `description` | No | Description of the instance's purpose | +| `data_validator_implementation_id` | Yes | UUID of the Data Validator implementation to instantiate | +| `onchain_data_validators` | Yes | Array of per-chain deployments with `chain_selector` and `initial_config` | + +Each on-chain Data Validator starts in `creation_pending` status until deployment completes. The response includes the instance `id` and the on-chain addresses per chain. + + + +## Update a Data Validator configuration + +You can update the allow and deny lists after deployment without redeploying the validator. Configuration changes use JSON Patch and are version-checked per chain for optimistic concurrency. + +Update the configuration with a `PATCH` request. Supply `on_chains` with the `current_config_version` for each chain you are changing: + +```bash +curl -X PATCH https://ace.api.chain.link/v1/data-validators//configs \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "patches": [ + { "op": "add", "path": "/allowlist/-", "value": "GB" } + ], + "on_chains": [ + { "chain_selector": "16015286601757825753", "current_config_version": "0" } + ] + }' +``` + +The JSON Patch format follows [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902). If the `current_config_version` does not match the on-chain state, the request is rejected — re-fetch the instance and retry with the current version. + +## Attach a Data Validator to a credential source + +A Data Validator takes effect only when it is referenced by a **credential source** on an identity-validation policy. Each credential source has a `dataValidator` field: + +- `0x0000000000000000000000000000000000000000` — attestation-only (default). The source checks only that the credential exists. +- A Data Validator address — the source additionally validates credential contents through that validator. + +Set the `dataValidator` field to your deployed Data Validator address when configuring the credential source on your [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) or [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) instance. See [Managing Policies — Update policy configuration](/ace/guides/policy-manager/manage-policies#update-policy-configuration) for how to change a policy instance's configuration. + + + +## View Data Validators + +List all Data Validators: + +```bash +curl https://ace.api.chain.link/v1/data-validators \ + -H "Authorization: Apikey " +``` + +| Parameter | Description | +| ---------------------------------- | ------------------------------------ | +| `page` | Page number (default: 1) | +| `page_size` | Results per page | +| `include_onchains` | Include per-chain deployment details | +| `data_validator_implementation_id` | Filter by implementation | +| `chain_selector` | Filter by chain | +| `address` | Filter by on-chain address | +| `status` | Filter by on-chain status | + +To retrieve a specific Data Validator by ID: + +```bash +curl https://ace.api.chain.link/v1/data-validators/ \ + -H "Authorization: Apikey " +``` + +## Archive a Data Validator + +Archiving a Data Validator deactivates the instance. Before archiving, detach it from any credential source that references it (set that source's `dataValidator` back to the zero address). + +Archive a Data Validator with a `PATCH` request: + +```bash +curl -X PATCH https://ace.api.chain.link/v1/data-validators/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "status": "archived" + }' +``` + +## Related pages + +- [Cross-Chain Identity — Credential data and privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy) — attestation-only vs. Data Validator checks +- [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types) — link a data schema to a credential type +- [Managing Credentials](/ace/guides/identity-manager/manage-credentials) — issue credentials with data +- [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) — the policy that consumes Data Validators via credential sources +- [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) — grouped identity validation with routing and Data Validators +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema diff --git a/src/content/ace/guides/policy-manager/manage-engines.mdx b/src/content/ace/guides/policy-manager/manage-engines.mdx new file mode 100644 index 00000000000..a438abb0aa9 --- /dev/null +++ b/src/content/ace/guides/policy-manager/manage-engines.mdx @@ -0,0 +1,266 @@ +--- +section: ace +title: "Managing Policy Engines" +date: Last Modified +metadata: + description: "Create, view, update, add or remove extractors, and archive ACE policy engines using the Platform UI or the Coordinator API." + datePublished: "2026-04-06" + lastModified: "2026-05-26" +--- + +import { Aside, ClickToZoom, MethodTabs } from "@components" + +A **policy engine** is the on-chain orchestrator that evaluates policies whenever a protected function is called. Each policy engine is deployed as a smart contract on one or more chains, and all your targets, policies, and protections are scoped to a specific engine. For a deeper explanation of how policy engines fit into the architecture, see [Architecture](/ace/concepts/architecture) and [Policy Management](/ace/concepts/policy-management). + + + +## Create a policy engine + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Policy Manager**. + 1. Click **Create a new engine**. + 1. Complete the three-step wizard: + - **Basic details** — enter an engine name and optional description. This metadata is internal and is not written onchain. + - **Networks** — select every chain where you want this engine deployed. Only networks enabled in your CRE account appear. If you do not see a network, contact your admin to ensure it is enabled in your CRE account first. Deploy a [CRE Connect Wallet](/ace/getting-started/account-setup#4-set-up-cre-connect-wallets) on each selected chain before creating the engine. + - **Contract types** — select **ERC-20** and/or **ERC-3643**. ACE attaches all pre-built extractors for the selected types automatically. + 1. Submit the wizard and wait for onchain deployment to complete. The engine starts in `creation_pending` status until deployment finishes. + 1. Open the engine in Policy Manager and confirm its status is **Active**. Click the **gear icon** to view the onchain contract addresses and attached extractors. + + For verification and troubleshooting during deployment, see [Create a PolicyEngine](/ace/getting-started/policy-manager#create-a-policyengine) in the Policy Manager Quick Start. + + + + + + Create a policy engine with a `POST` request. Each entry in `onchain_policy_engines` specifies a `chain_selector` for a target chain (see [Supported Networks](/ace/supported-networks) for available chain selectors): + + ```bash + curl -X POST https://ace.api.chain.link/v1/policy-engines \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "Production Policy Engine", + "description": "Main policy engine for compliance enforcement", + "onchain_policy_engines": [ + { "chain_selector": "16015286601757825753" }, + { "chain_selector": "3478487238524512106" }, + { "chain_selector": "14767482510784806043" }, + { "chain_selector": "16281711391670634445" }, + { "chain_selector": "10344971235874465080" } + ] + }' + ``` + + | Field | Required | Description | + | --- | --- | --- | + | `name` | Yes | Human-readable name | + | `description` | No | Description of the engine's purpose | + | `extractor_ids` | No | Array of extractor UUIDs to register with the engine | + | `onchain_policy_engines` | Yes | Array of objects with `chain_selector` values for each deployment chain | + + + + Each on-chain engine starts in `creation_pending` status until deployment completes. The response includes the engine `id` and the on-chain contract addresses. + + + + +## View policy engines + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Policy Manager** in the left sidebar. You see all the policy engines in your organization. + 1. Click on a policy engine to open it. The detail view has two tabs: + - **Contracts** — lists the target contracts protected by this policy engine. + - **Policy types** — lists all policy instances created for this policy engine. + + + + 1. To view the engine's on-chain details, click the **gear icon** (shown in the screenshot above) to open the settings page. There you can see: + - The **PolicyEngine contract addresses** on each network the engine is deployed to. + - The **Extractors** currently attached to this policy engine. + + + + + + List all policy engines: + + ```bash + curl https://ace.api.chain.link/v1/policy-engines \ + -H "Authorization: Apikey " + ``` + + | Parameter | Description | + | --- | --- | + | `page` | Page number (default: 1) | + | `page_size` | Results per page (max: 100) | + | `include_onchains` | Include on-chain deployment details (default: true) | + + To retrieve a specific engine by ID: + + ```bash + curl https://ace.api.chain.link/v1/policy-engines/ \ + -H "Authorization: Apikey " + ``` + + + + +## Update a policy engine + +You can update a policy engine's name, description, and extractor associations. + + + + + + + Update a policy engine with a `PUT` request. Both `name` and `onchain_policy_engines` are required: + + ```bash + curl -X PUT https://ace.api.chain.link/v1/policy-engines/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "Production Policy Engine (Updated)", + "description": "Updated description", + "extractor_ids": [""], + "onchain_policy_engines": [ + { "chain_selector": "16015286601757825753" }, + { "chain_selector": "3478487238524512106" }, + { "chain_selector": "14767482510784806043" }, + { "chain_selector": "16281711391670634445" }, + { "chain_selector": "10344971235874465080" } + ] + }' + ``` + + + + +## Add or remove extractors + +Extractors decode transaction calldata into named parameters (sender, recipient, amount, etc.) so policies can evaluate them. If you forgot to attach an extractor during engine creation or need to remove one, use the `PUT /policy-engines/{id}` endpoint. + + + +### Add a missing extractor + +First, retrieve your engine to see which extractors are currently attached: + +```bash +curl https://ace.api.chain.link/v1/policy-engines/ \ + -H "Authorization: Apikey " +``` + +Check the `extractor_registrations` array in the response. Then send a PUT request that includes the existing extractor IDs plus the new one: + +```bash +curl -X PUT https://ace.api.chain.link/v1/policy-engines/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "My Policy Engine", + "extractor_ids": [ + "d4b8cd51-7d5a-487a-9ba5-bb7236e3184c", + "572201bb-170b-4fda-ab89-a65b5bbc594b", + "f17bbe8b-8462-4dd7-8fb7-a4973dff04fc" + ], + "onchain_policy_engines": [ + { "chain_selector": "16015286601757825753" }, + { "chain_selector": "3478487238524512106" }, + { "chain_selector": "14767482510784806043" }, + { "chain_selector": "16281711391670634445" }, + { "chain_selector": "10344971235874465080" } + ] + }' +``` + +In this example, `f17bbe8b-8462-4dd7-8fb7-a4973dff04fc` is the new extractor being added alongside two that were already attached. + +### Remove an extractor + +Send a PUT request with the `extractor_ids` array that **omits** the extractor you want to detach: + +```bash +curl -X PUT https://ace.api.chain.link/v1/policy-engines/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "My Policy Engine", + "extractor_ids": [ + "d4b8cd51-7d5a-487a-9ba5-bb7236e3184c", + "572201bb-170b-4fda-ab89-a65b5bbc594b" + ], + "onchain_policy_engines": [ + { "chain_selector": "16015286601757825753" }, + { "chain_selector": "3478487238524512106" }, + { "chain_selector": "14767482510784806043" }, + { "chain_selector": "16281711391670634445" }, + { "chain_selector": "10344971235874465080" } + ] + }' +``` + + + +## Archive a policy engine + +Archiving a policy engine deactivates it and prevents any further operations. All policy instances associated with the engine must be archived first. + + + + + + + Archive a policy engine with a `PATCH` request: + + ```bash + curl -X PATCH https://ace.api.chain.link/v1/policy-engines/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "status": "archived" + }' + ``` + + + + + + +## Related pages + +- [Architecture](/ace/concepts/architecture) — how PolicyEngine contracts fit into the ACE system +- [Policy Management](/ace/concepts/policy-management) — how policy chains and evaluation work +- [Managing Targets](/ace/guides/policy-manager/manage-targets) — register contracts to protect under an engine +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — create and configure policy instances +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema diff --git a/src/content/ace/guides/policy-manager/manage-policies.mdx b/src/content/ace/guides/policy-manager/manage-policies.mdx new file mode 100644 index 00000000000..cb235e63c13 --- /dev/null +++ b/src/content/ace/guides/policy-manager/manage-policies.mdx @@ -0,0 +1,207 @@ +--- +section: ace +title: "Managing Policies" +date: Last Modified +metadata: + description: "Browse policy implementations, create and configure policy instances, and update policy configurations in Chainlink ACE using the Platform UI or the Coordinator API." + datePublished: "2026-04-15" + lastModified: "2026-04-15" +--- + +import { Aside, ClickToZoom, MethodTabs } from "@components" +import PolicyInstanceBuilder from "../../../../components/ACE/PolicyInstanceBuilder/PolicyInstanceBuilder.tsx" + +This guide covers how to browse available policy types, create policy instances, and configure their parameters. For attaching policies to specific functions on your contracts, see [Protecting Target Functions](/ace/guides/policy-manager/manage-protections). + +## Policy implementations vs policy instances + +ACE distinguishes between two concepts: + +- **Policy implementation** — A reusable policy template (smart contract code) that defines specific compliance logic, such as an allowlist check or volume limit. ACE provides a [pre-built library](/ace/reference/policy-library) of audited implementations, and you can register your own [custom policy](/ace/guides/policy-manager/custom-policies) implementations, which are scoped to your organization. +- **Policy instance** — A deployed copy of a policy implementation, configured with your specific parameters and associated with a [policy engine](/ace/guides/policy-manager/manage-engines). You create a policy instance from an implementation and then attach it to target functions via [protections](/ace/guides/policy-manager/manage-protections). + +For example, the "Allow List Policy" implementation can be instantiated multiple times with different allowlists. + +## Browse policy implementations + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Policy Manager** in the left sidebar. + 1. Click on the policy engine you want to browse policies for. + 1. Select the **Policy types** tab. You see a list of all available policy implementations with their descriptions. + + + + + + List all policy implementations: + + ```bash + curl https://ace.api.chain.link/v1/policy-implementations \ + -H "Authorization: Apikey " + ``` + + | Parameter | Description | + | --- | --- | + | `page` | Page number (default: 1) | + | `page_size` | Results per page (max: 100) | + | `include_onchains` | Include on-chain contract details (default: true) | + + To retrieve a specific implementation by ID: + + ```bash + curl https://ace.api.chain.link/v1/policy-implementations/ \ + -H "Authorization: Apikey " + ``` + + + + +## Create a policy instance + +A policy instance is created from a policy implementation and deployed on-chain within a policy engine. + + + + 1. From the **Policy types** tab (see [Browse policy implementations](#browse-policy-implementations) above), click on the policy implementation you want to use. + 1. Click the **+ Add instance** button. + 1. Follow the wizard to configure and create your policy instance. + + + Select a policy implementation below to see its configuration fields and get a ready-to-use curl command. Each implementation has a `policy_config_schema` that describes its configurable fields — the tool below extracts the key information you need. + + + + | Field | Required | Description | + | --- | --- | --- | + | `name` | Yes | Human-readable name for the policy instance | + | `description` | No | Description of the instance's purpose | + | `policy_implementation_id` | Yes | UUID of the policy implementation to instantiate | + | `policy_engine_id` | Yes | UUID of the policy engine to associate with | + | `onchain_policies` | No | Array of per-chain deployments with `chain_selector` and `initial_config` | + + Each on-chain policy starts in `creation_pending` status until deployment completes. + + + + + + +## View and filter policies + + + + 1. From the **Policy types** tab (see [Browse policy implementations](#browse-policy-implementations) above), click on the policy implementation type you are interested in. + 1. The list of policy instances created for that implementation type is displayed. + + + List all policy instances: + + ```bash + curl https://ace.api.chain.link/v1/policies \ + -H "Authorization: Apikey " + ``` + + | Parameter | Description | + | --- | --- | + | `page` | Page number (default: 1) | + | `page_size` | Results per page (max: 100) | + | `include_onchains` | Include on-chain deployment details (default: true) | + | `target_id` | Filter by target | + | `policy_engine_id` | Filter by policy engine | + | `name` | Filter by name | + | `status` | Filter by on-chain status (`creation_pending`, `creation_failed`, `created`) | + | `only_with_active_protections` | Return only policies attached to at least one target function | + | `target_address` | Filter by target contract address | + + To retrieve a specific policy by ID: + + ```bash + curl https://ace.api.chain.link/v1/policies/ \ + -H "Authorization: Apikey " + ``` + + + + +## Update policy configuration + +After deploying a policy instance, you can update its on-chain configuration parameters — for example, adding an address to an allowlist or changing a volume threshold — without redeploying the policy. + + + + 1. From the policy instances list (see [View and filter policies](#view-and-filter-policies) above), click on the policy instance you want to update. + 1. A detail drawer opens on the right side of the screen. + 1. Click the **Edit configuration** button to modify the policy's parameters. + 1. Follow the wizard to update the policy's configuration. + + + Update policy configurations with a `PATCH` request using JSON patch operations: + + ```bash + curl -X PATCH https://ace.api.chain.link/v1/policies//configs \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "patches": [ + { + "op": "add", + "path": "/allowList/-", + "value": "0x3333333333333333333333333333333333333333" + } + ] + }' + ``` + + The JSON patch format follows [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902). Common operations: + + | Operation | Description | Example | + | --- | --- | --- | + | `add` | Add a value | Add an address to an allowlist | + | `remove` | Remove a value | Remove an address from a list | + | `replace` | Replace a value | Change a threshold | + + + + +## Archive a policy + +Archiving a policy instance removes it from active use. All target protections that reference this policy must be archived first. + + + + + + + Archive a policy with a `PATCH` request: + + ```bash + curl -X PATCH https://ace.api.chain.link/v1/policies/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "status": "archived" + }' + ``` + + + + + + +## Related pages + +- [Policy Management](/ace/concepts/policy-management) — how policy chains and evaluation work +- [Policy Library](/ace/reference/policy-library) — pre-built policy implementations with configuration details +- [Managing Policy Engines](/ace/guides/policy-manager/manage-engines) — create the engine your policies belong to +- [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) — attach policy instances to target functions +- [Policy Ordering & Composition](/ace/concepts/policy-ordering) — how to compose effective rulesets +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema diff --git a/src/content/ace/guides/policy-manager/manage-protections.mdx b/src/content/ace/guides/policy-manager/manage-protections.mdx new file mode 100644 index 00000000000..cc56b8e3177 --- /dev/null +++ b/src/content/ace/guides/policy-manager/manage-protections.mdx @@ -0,0 +1,220 @@ +--- +section: ace +title: "Protecting Target Functions" +date: Last Modified +metadata: + description: "Create, view, update, and archive target protections to bind policy instances to specific contract functions in Chainlink ACE using the Platform UI or the Coordinator API." + datePublished: "2026-04-06" + lastModified: "2026-05-26" +--- + +import { Aside, MethodTabs } from "@components" + +A **target protection** is the link between a [policy instance](/ace/guides/policy-manager/manage-policies) and a specific function on a [target contract](/ace/guides/policy-manager/manage-targets). When a user calls the protected function, the policy engine evaluates the bound policies in order and decides whether to allow or reject the transaction. Target protections are the final step in setting up on-chain compliance enforcement. + +## Prerequisites + +Before creating a target protection, you need: + +1. A [policy engine](/ace/guides/policy-manager/manage-engines) deployed on your target chains. +1. A [target contract](/ace/guides/policy-manager/manage-targets) registered under that engine. +1. A [policy instance](/ace/guides/policy-manager/manage-policies) created from a policy implementation and associated with the same engine. +1. Extractors attached to the engine that support the function signatures you want to protect (see the [Policy Manager Quick Start](/ace/getting-started/policy-manager#extractor-ids-for-api-creation) for the full list). + +## Create a target protection + +A protection binds a policy instance to a specific function on your target contract. Once created, every call to that function is evaluated against the policy. + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Policy Manager** in the left sidebar. + 1. Click on the policy engine that manages your target contract. + 1. In the **Contracts** tab, click on the target contract you want to protect. + 1. Click the **Attach Policy** button. + 1. **Step 1 — Select policy:** Choose the policy type and the specific policy instance you want to use. + 1. **Step 2 — Define protected functions:** Use the **Contract functions** dropdown to select a function to protect. For each function, choose the **Execution order**: + - **Last (default)** — the policy is added at the end of the evaluation chain. + - **First** — the policy is evaluated before all other policies on this function. + + To protect multiple functions with the same policy, click **Add more** and repeat for each function. + + + + 1. **Step 3 — Review and deploy:** Confirm your selections and deploy the protection. + + + + Create a target protection with a `POST` request: + + ```bash + curl -X POST https://ace.api.chain.link/v1/targets//protections \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "function_signature": "transfer(address,uint256)", + "policy_instance_id": "", + "desired_position": 0, + "extractor_output_ids": [ + "", + "" + ], + "onchain_target_protections": [ + { "chain_selector": "16015286601757825753" } + ] + }' + ``` + + | Field | Required | Description | + | --- | --- | --- | + | `function_signature` | Yes | The function to protect (e.g., `transfer(address,uint256)`) | + | `policy_instance_id` | Yes | UUID of the policy instance to bind | + | `desired_position` | No | Evaluation order (0 is first). Determines where in the policy chain this policy runs | + | `extractor_output_ids` | No | Array of extractor output UUIDs mapping extracted calldata to the policy's parameters | + | `onchain_target_protections` | No | Array of objects with `chain_selector` to specify deployment chains | + + Each on-chain protection starts in `creation_pending` status until the `AddPolicy` call completes. The combination of policy instance, target, and function selector must be unique. + + + + +## Position and evaluation order + +The `desired_position` determines the order in which policies are evaluated when a protected function is called: + +- **Position 0** is evaluated first. +- Policies are evaluated sequentially. If a policy returns **Reject**, the transaction is reverted immediately and remaining policies are not evaluated. +- If all policies return **Allow**, or if no policy explicitly rejects, the `desired_default_allow` setting on the [target](/ace/guides/policy-manager/manage-targets#default-allow-behavior) determines the outcome. + +For detailed information on composing effective rulesets, see [Policy Ordering & Composition](/ace/concepts/policy-ordering). + +## View protections + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Policy Manager** in the left sidebar. + 1. Click on the policy engine, then click on the target contract. + 1. Choose one of two views: + - **Functions** — groups protections by function. You see two sections: **Unprotected functions** (no policies attached) and **Protected functions** (with the policies protecting each function and their deployment status). + - **Policies** — groups protections by policy instance. Each policy card shows which functions it protects. + + You can also filter by **Policy** or **Status** using the dropdowns at the top. + + + + List all protections for a target: + + ```bash + curl https://ace.api.chain.link/v1/targets//protections \ + -H "Authorization: Apikey " + ``` + + | Parameter | Description | + | --- | --- | + | `page` | Page number (default: 1) | + | `page_size` | Results per page (max: 100) | + | `include_onchains` | Include per-chain deployment details (default: true) | + | `function_signature` | Filter by function signature | + | `policy_instance_id` | Filter by policy instance | + | `status` | Filter by on-chain status (`creation_pending`, `created`, `removal_pending`, etc.) | + + To retrieve a specific protection: + + ```bash + curl https://ace.api.chain.link/v1/targets//protections/ \ + -H "Authorization: Apikey " + ``` + + + + +## Manage an existing protection + +From the **Functions** or **Policies** view on your target contract (see [View protections](#view-protections) above), click on a policy instance to open a detail drawer. From there you can: + +- **Detach policy** — removes the protection so the policy no longer evaluates this function (see [Archive a protection](#archive-a-protection) below). +- **Edit instance** — opens the policy instance configuration (see [Update policy configuration](/ace/guides/policy-manager/manage-policies#update-policy-configuration)). + +### Extend a protection to additional chains + +If you created a protection on one chain and later want it to apply on additional chains, you can extend it via the API. + + + + + + + Extend a protection to additional chains with a `PUT` request: + + ```bash + curl -X PUT https://ace.api.chain.link/v1/targets//protections/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "onchain_target_protections": [ + { "chain_selector": "16015286601757825753" }, + { "chain_selector": "3478487238524512106" } + ] + }' + ``` + + + + + + +### Archive a protection + +Archiving a protection unbinds the policy from the function. Once archived, the policy no longer evaluates transactions on that function. + + + + 1. From the **Functions** or **Policies** view on your target contract, click on the policy instance you want to detach. + 1. In the detail drawer, click **Detach policy**. + + + Archive a protection with a `PATCH` request: + + ```bash + curl -X PATCH https://ace.api.chain.link/v1/targets//protections/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "status": "archived" + }' + ``` + + + + + + +## Archival dependency chain + +ACE enforces an ordered archival flow. You must archive resources from the outside in: + +1. **Target protections** — archive these first +1. **Policy instances** — archive after all protections referencing them are archived +1. **Targets** — archive after all protections on the target are archived +1. **Policy engines** — archive after all policies in the engine are archived + +## Related pages + +- [Policy Ordering & Composition](/ace/concepts/policy-ordering) — how evaluation order affects transaction outcomes +- [Managing Policy Engines](/ace/guides/policy-manager/manage-engines) — create the engine that manages policies +- [Managing Targets](/ace/guides/policy-manager/manage-targets) — register contracts to protect +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — create and configure policy instances +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema diff --git a/src/content/ace/guides/policy-manager/manage-targets.mdx b/src/content/ace/guides/policy-manager/manage-targets.mdx new file mode 100644 index 00000000000..77f2bcac7ba --- /dev/null +++ b/src/content/ace/guides/policy-manager/manage-targets.mdx @@ -0,0 +1,279 @@ +--- +section: ace +title: "Managing Targets" +date: Last Modified +metadata: + description: "Register, view, configure, and archive target contracts in Chainlink ACE using the Platform UI or the Coordinator API." + datePublished: "2026-04-06" + lastModified: "2026-04-06" +--- + +import { Aside, MethodTabs } from "@components" + +A **target** is a smart contract protected by ACE. After deploying your [ACE-compatible contract](/ace/guides/policy-manager/contracts/ace-compatible), you register it as a target under a PolicyEngine. Once registered, you can configure the [default policy result](#default-allow-behavior) and attach [policies](/ace/guides/policy-manager/manage-policies) to its functions via [protections](/ace/guides/policy-manager/manage-protections). + + + +## Register a target + +After deploying your contract, register it as a target with a `POST` request. Provide the contract name, type, protected methods, and on-chain addresses: + +```bash +curl -X POST https://ace.api.chain.link/v1/targets \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "title": "My ERC-20 Token", + "description": "Production ERC-20 token with compliance enforcement", + "policy_engine_id": "", + "protected_methods": [ + "transfer(address,uint256)", + "transferFrom(address,address,uint256)" + #[any other methods you want to protect] + ], + "desired_default_allow": true, + "metadata": {"contract_type": "ERC-20"}, + "onchain_targets": [ + { + "chain_selector": "16015286601757825753", + "address": "0xYourContractAddressOnSepolia" + }, + { + "chain_selector": "3478487238524512106", + "address": "0xYourContractAddressOnArbitrumSepolia" + }, + #[any other chains where your contract is deployed] + ] + }' +``` + +| Field | Required | Description | +| ----------------------- | -------- | ------------------------------------------------------------- | +| `title` | Yes | Human-readable name for the target | +| `description` | No | Description of the contract | +| `policy_engine_id` | Yes | UUID of the policy engine to associate with | +| `protected_methods` | No | Array of function signatures that can be protected | +| `desired_default_allow` | No | Whether to allow transactions by default (default: `true`) | +| `onchain_targets` | No | Array of objects with `chain_selector` and contract `address` | +| `metadata` | No | Arbitrary JSON metadata (e.g., `{"contract_type": "ERC-20"}`) | + +## Default allow behavior + +The **default policy result** controls what happens when a transaction passes through the entire policy chain and no policy explicitly returns Allow or Reject (i.e., every policy returns Continue). This is configured per target contract via the `desired_default_allow` field: + +- **`true` (default)** — The transaction is allowed. This is appropriate when you want policies to act as blockers (reject specific cases), and everything else passes through. +- **`false`** — The transaction is rejected. This is appropriate for allowlist-style enforcement where only explicitly approved transactions proceed. + +For more on how policy evaluation ordering works, see [Policy Ordering & Composition](/ace/concepts/policy-ordering#the-default-result). + +### Change the default policy result + + + + + + + Update an existing target's default with a `PUT` request. Set `desired_default_allow` to `true` (allow by default) or `false` (reject by default). Because PUT is a full replacement, include all fields you want to keep: + + ```bash + curl -X PUT https://ace.api.chain.link/v1/targets/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "title": "My ERC-20 Token", + "description": "Production ERC-20 token with compliance enforcement", + "protected_methods": [ + "transfer(address,uint256)", + "transferFrom(address,address,uint256)" + ], + "desired_default_allow": false, + "metadata": {"contract_type": "ERC-20"} + }' + ``` + + The Coordinator calls `SetTargetDefaultPolicyAllow` on the PolicyEngine contract for each chain where the target is deployed. + + + + +## View targets + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Policy Manager** in the left sidebar. You see all the policy engines in your organization. + 1. Click on a policy engine to open it. The **Contracts** tab lists all target contracts protected by this policy engine. + 1. Click on a target contract card to see its details. You can switch between two views: + - **Functions** — shows unprotected and protected functions, along with the policies protecting each protected function. + - **Policies** — shows the list of policies protecting this target contract and which specific functions each policy applies to. + + + List all targets: + + ```bash + curl https://ace.api.chain.link/v1/targets \ + -H "Authorization: Apikey " + ``` + + | Parameter | Description | + | --- | --- | + | `page` | Page number (default: 1) | + | `page_size` | Results per page (max: 100) | + | `include_onchains` | Include on-chain contract details (default: true) | + | `policy_engine_id` | Filter by policy engine | + | `chain_selector` | Filter by chain | + | `search` | Search by target title or on-chain address | + + To retrieve a specific target by ID: + + ```bash + curl https://ace.api.chain.link/v1/targets/ \ + -H "Authorization: Apikey " + ``` + + + + +## Update a target + +You can update a target's name, description, contract type, protected methods, default allow behavior, and on-chain addresses. + + + + + + 1. In the [ACE Platform](https://app.chain.link), go to **Compliance > Policy Manager** and click on your policy engine. + 1. In the **Contracts** tab, click on the target contract you want to update. + 1. Click the **Edit** icon in the contract header. A drawer opens on the right. + 1. Update the **Name**, **Contract type**, or **Description** as needed. + 1. Click **Save changes**. + + + + + + Update a target with a `PUT` request. Include all fields you want to preserve: + + ```bash + curl -X PUT https://ace.api.chain.link/v1/targets/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "title": "My ERC-20 Token (Updated)", + "description": "Updated description", + "protected_methods": [ + "transfer(address,uint256)", + "transferFrom(address,address,uint256)", + "mint(address,uint256)" + ], + "desired_default_allow": true, + "metadata": {"contract_type": "ERC-20"} + }' + ``` + + + + +## Link targets + +When you deploy your ACE-compatible contract on a new chain, the control plane detects it automatically and creates a separate **detected target** (titled "unknown target"). Rather than managing each chain deployment as its own target, you can **merge** detected targets into an existing target to keep a single multi-chain target with all its on-chain addresses in one place. + +### Conditions + +A detected target can be merged (linked) into an existing target when: + +- The detected target was auto-discovered — it still has the default "unknown target" title. +- The detected target has at least one on-chain address. +- A valid destination target exists in the same policy engine: it must be a different target, already named, and deployed on a **different chain** than the source (no shared chain selectors). + +If no valid destination exists, the detected target cannot be merged — you can only rename it via **Edit details**. + +### How it works + +Merging transfers the on-chain addresses from the source target(s) to the destination target, then archives the sources. After the merge, the destination target contains all chain deployments and any protections remain on the destination. + + + + 1. In the [Chainlink Platform](https://app.chain.link), go to **Compliance > Policy Manager** and open your policy engine. + 1. In the **Contracts** tab, locate the detected contract (shown as "unknown target"). + 1. Click the **three-dot menu** on the detected contract card. + 1. Click **Link to existing contract**. + 1. Select the destination contract you want to merge into. + 1. Confirm the merge. The detected contract's on-chain address is transferred to the destination and the detected target is archived. + + + Merge one or more source targets into a destination target with a `POST` request: + + ```bash + curl -X POST "https://ace.api.chain.link/v1/targets//merge" \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "source_target_ids": [""] + }' + ``` + + |
Field
| Required | Description | + | --- | --- | --- | + | `source_target_ids` | Yes | Array of UUID(s) — the detected targets whose on-chain addresses will be transferred to the destination | + + The response returns the updated destination target with all merged on-chain addresses. The source targets are archived automatically. + +
+
+ + + +## Archive a target + +Archiving a target removes it from active use. All target protections associated with the target must be archived first. + + + + + + + Archive a target with a `PATCH` request: + + ```bash + curl -X PATCH https://ace.api.chain.link/v1/targets/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "status": "archived" + }' + ``` + + + + + + +## Related pages + +- [Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible) — how to integrate `PolicyProtected` into your contract +- [Managing Policy Engines](/ace/guides/policy-manager/manage-engines) — create the engine your target will use +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — create policy instances to attach to your target +- [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) — attach policies to specific functions on your target +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema diff --git a/src/content/ace/guides/policy-manager/offchain-policies/grant-evaluation-access.mdx b/src/content/ace/guides/policy-manager/offchain-policies/grant-evaluation-access.mdx new file mode 100644 index 00000000000..bf3b41ab990 --- /dev/null +++ b/src/content/ace/guides/policy-manager/offchain-policies/grant-evaluation-access.mdx @@ -0,0 +1,133 @@ +--- +section: ace +title: "Granting Evaluation Access" +date: Last Modified +metadata: + description: "Grant another organization access to request offchain permit evaluations against your protected target contract. Manage evaluation access grants for cross-organization compliance workflows." + datePublished: "2026-07-17" + lastModified: "2026-07-17" +--- + +import { Aside } from "@components" + +By default, only the organization that owns a target contract can request offchain permit evaluations for it through the [Evaluation API](/ace/guides/policy-manager/offchain-policies/request-offchain-permits). **Evaluation access grants** let you extend this capability to other organizations — for example, allowing a DEX or lending protocol to request permits against your token's compliance rules. + +When you grant evaluation access, the grantee organization can call the Evaluation API for the specified target and offchain policy. The evaluation runs against **your** managed CRE workflow and risk configuration — the grantee does not need its own offchain policy or TRM credential. + + + +## Roles and concepts + +|
Term
| Meaning | +| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Grantor** | The organization that **owns** the target contract and the offchain policy, and grants evaluation access. | +| **Grantee** | The organization that **receives** evaluation access. It can call the Evaluation API for the specified target and policy. | +| **Access grant** | The link between an offchain policy–target pair and a grantee organization. It is either `active` or `revoked`. | +| **Org ID** | The identifier of an organization. The grantee shares theirs with the grantor so the grantor can create the grant. Retrieve it with `GET /organizations/me` (Coordinator API). | + +## What the grantee can and cannot do + +An active evaluation access grant lets the grantee: + +- **Call the Evaluation API** (`POST /evaluate`) for the granted target and offchain policy. The evaluation uses the grantor's managed workflow and TRM configuration. +- **Poll evaluation status** (`GET /evaluate/{permitId}`) for evaluations the grantee started. +- **List granted targets** using `GET /targets?include_granted=true` to discover targets other organizations have shared with them. + +The grantee **cannot**: + +- Modify the offchain policy, its risk thresholds, or the protection configuration. +- Manage the target contract, its policy engine, or any other resource owned by the grantor. +- Re-share evaluation access with a third organization. + + + +## Prerequisites + +Before granting evaluation access: + +1. You have a [managed offchain policy](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) with `deployment_status: active`. +1. The offchain policy has an active [protection](/ace/guides/policy-manager/manage-protections) on the target function. +1. You know the grantee's **Org ID**. Ask them to retrieve it: + +```bash +# Run by the grantee +curl https://ace.api.chain.link/v1/organizations/me \ + -H "Authorization: Apikey " +``` + +## Grant evaluation access + +As the target and policy owner, create the grant by specifying the offchain policy ID, target ID, and the grantee's Org ID: + +```bash +curl -X POST https://ace.api.chain.link/v1/policies//targets//access-grants \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "grantee_org_id": "" + }' +``` + +The response is the created grant: + +```json +{ + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "grantee_org_id": "org-456", + "grantor_org_id": "org-123", + "status": "active", + "granted_at": 1800000000, + "revoked_at": null +} +``` + +## View who has access + +List the active and past grants for a specific offchain policy and target pair: + +```bash +curl https://ace.api.chain.link/v1/policies//targets//access-grants \ + -H "Authorization: Apikey " +``` + +Each entry includes the grantee, the status (`active` or `revoked`), and timestamps, giving you an audit trail of who was granted access and when. + +## Discover granted targets (grantee) + +As a grantee, include `include_granted=true` when listing targets to see targets other organizations have shared with you, alongside your own: + +```bash +curl "https://ace.api.chain.link/v1/targets?include_granted=true" \ + -H "Authorization: Apikey " +``` + +Once you can see a granted target, you can call the Evaluation API for it the same way you would for your own targets. See [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) for the full evaluation workflow. + +## Revoke access + +As the policy and target owner, revoke a grant by setting its status to `revoked`: + +```bash +curl -X PATCH \ + https://ace.api.chain.link/v1/policies//targets//access-grants/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "status": "revoked" + }' +``` + +Revocation takes effect immediately. The grantee can no longer request evaluations for this target and policy. The grant record is retained with a `revoked_at` timestamp for audit purposes. To restore access later, create a new grant. + +## Related pages + +- [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) — call the Evaluation API and submit the protected transaction +- [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) — configure TRM screening and attach protections +- [External Registries](/ace/guides/identity-manager/external-registries) — a similar grant model for sharing identity and credential registries +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema diff --git a/src/content/ace/guides/policy-manager/offchain-policies/index.mdx b/src/content/ace/guides/policy-manager/offchain-policies/index.mdx new file mode 100644 index 00000000000..b646b7988bc --- /dev/null +++ b/src/content/ace/guides/policy-manager/offchain-policies/index.mdx @@ -0,0 +1,34 @@ +--- +section: ace +title: "Offchain Policies" +isIndex: true +date: Last Modified +metadata: + description: "Understand the Chainlink ACE offchain policy models and configure the managed wallet risk screening MVP." + datePublished: "2026-07-17" + lastModified: "2026-07-17" +--- + +import { Aside } from "@components" + +ACE offchain policies evaluate data or logic outside the blockchain before authorizing a protected onchain action. The authorization is delivered onchain as a permit that is bound to a specific transaction intent. + +ACE supports two offchain policy models: + +- **Managed offchain policies** provide an out-of-the-box workflow. You configure the risk rules and protections, while Chainlink manages the CRE workflow, external provider call, onchain validator deployment, and permit delivery. The current implementation supports wallet risk screening with TRM Wallet Screening. +- **Custom offchain policies** support bespoke compliance logic and providers. You host the policy endpoint and work with Chainlink to configure and operate the integration. + + + +## Managed offchain policy guides + +- [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) — meet the TRM and CRE prerequisites, configure wallet risk rules, create the policy, and attach protections to target functions. +- [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) — call the Evaluation API, monitor an evaluation, and submit the protected transaction after its permit is ready onchain. +- [Granting Evaluation Access](/ace/guides/policy-manager/offchain-policies/grant-evaluation-access) — let another organization request permit evaluations against your protected target contract. + +## Learn how offchain policies work + +See [Offchain Policies](/ace/concepts/off-chain-policies) for the architecture and execution flows of both managed and custom offchain policy models. diff --git a/src/content/ace/guides/policy-manager/offchain-policies/manage-offchain-policies.mdx b/src/content/ace/guides/policy-manager/offchain-policies/manage-offchain-policies.mdx new file mode 100644 index 00000000000..9f4931028f0 --- /dev/null +++ b/src/content/ace/guides/policy-manager/offchain-policies/manage-offchain-policies.mdx @@ -0,0 +1,288 @@ +--- +section: ace +title: "Managing Offchain Policies (MVP)" +date: Last Modified +metadata: + description: "Configure a managed Chainlink ACE offchain policy that screens wallet addresses with TRM Wallet Screening before protected transactions execute." + datePublished: "2026-07-17" + lastModified: "2026-07-17" +--- + +import { Aside } from "@components" + +ACE managed offchain risk policies screen wallet addresses with [TRM Wallet Screening](https://www.trmlabs.com/blockchain-intelligence-platform/wallet-screening) before allowing a protected onchain action. You configure the risk rules and the target functions to protect. Chainlink manages the CRE workflow, deploys the onchain permit validator, calls TRM, and delivers approved permits onchain. + +This guide covers policy setup. To integrate permit requests into your application, see [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits). + + + +## How the managed policy works + +Creating a managed offchain policy provisions two components: + +- A managed CRE workflow that screens the configured wallet addresses with TRM Wallet Screening. +- A [CertifiedActionDONValidatorPolicy](/ace/reference/policy-library/certified-action-don-validator-policy) (CADV) contract on each selected chain. The workflow writes approved permits to this contract through the Keystone Forwarder. + +When you attach the policy to a target function, the CADV becomes part of that function's policy chain. A call without a matching permit is rejected. A permit is valid only for its caller, target, function, and extracted parameters. + +## Prerequisites + +Before creating a managed offchain risk policy, you need: + +1. An ACE organization and [ACE API key](/ace/getting-started/account-setup#3-create-an-api-key). +1. A [policy engine](/ace/guides/policy-manager/manage-engines) deployed on every chain where you want to use the policy. +1. A [target contract](/ace/guides/policy-manager/manage-targets) associated with that policy engine. +1. An extractor attached to the policy engine that supports the target function. The extractor outputs determine which transaction parameters the permit must match. +1. A Chainlink CRE account with the [CRE CLI](https://docs.chain.link/cre/getting-started/cli-installation/macos-linux) installed and authenticated. +1. A TRM Labs account with Wallet Screening API access and a valid API key. ACE does not provide a TRM account or API credentials. See [TRM Wallet Screening](https://www.trmlabs.com/blockchain-intelligence-platform/wallet-screening) to learn about the product and request access. + +## Store the TRM credential in Vault DON + +The managed workflow retrieves your TRM credential from Vault DON at runtime. The credential remains encrypted and is not included in the offchain policy configuration. + +TRM uses HTTP Basic authentication with the API key as both the username and password. Before uploading it, encode `:` as Base64 without a trailing newline: + +```bash +export TRM_API_KEY="" +export TRM_BASIC_AUTH=$(printf '%s:%s' "$TRM_API_KEY" "$TRM_API_KEY" | base64 | tr -d '\n') +``` + +Create a secrets file that maps the Vault DON secret identifier to the environment variable: + +```yaml +secretsNames: + trmApiKey: + - TRM_BASIC_AUTH +``` + +Upload the secret using the CRE CLI. Replace `` with your CRE target: + +```bash +cre secrets create production-secrets.yaml \ + --target \ + --secrets-auth=browser +``` + +The identifier under `secretsNames` is the value to use for `secret_name` when you create the policy. In this example, it is `trmApiKey`. + +For prerequisites, authentication options, secret lifecycle operations, and troubleshooting, see [Using Secrets with Deployed Workflows](https://docs.chain.link/cre/guides/workflow/secrets/using-secrets-deployed). + + + +## Configure the risk policy + +The `wallet_risk_scoring` policy supports the following configuration: + +|
Field
| Required | Description | +| ------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `secret_name` | Yes | Vault DON identifier containing the Base64-encoded TRM Basic Auth credential. | +| `addresses_to_check` | Yes | Which addresses to screen: `CALLER`, `PARAMETERS`, or `ALL`. | +| `risk_threshold` | Yes | Reject an address whose highest TRM risk level is at or above this threshold: `LOW`, `MEDIUM`, `HIGH`, or `SEVERE`. | +| `block_unknown` | No | When `true`, reject an address whose TRM risk level is `UNKNOWN`. Defaults to `false`. | +| `category_filters` | No | Category-specific thresholds. Each entry contains `category` and an optional `threshold`. If omitted, the global `risk_threshold` applies to that category. | +| `fail_mode` | No | `CLOSED` fails the evaluation when TRM returns an unsuccessful HTTP response. `OPEN` allows it to continue. Defaults to `CLOSED`. | + +### Select addresses to screen + +The `addresses_to_check` setting controls which addresses are sent to TRM: + +|
Value
| Addresses screened | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CALLER` | Only `caller_address` from the evaluation request. | +| `PARAMETERS` | Addresses found in `permit_parameters`. The first permit parameter represents the sender; subsequent address values are identified from the function signature. | +| `ALL` | The caller and all addresses found in `permit_parameters`, with duplicates removed. | + +For an ERC-20 `transfer(address,uint256)` evaluation with permit parameters `[from, to, amount]`, `CALLER` screens `from`, while `PARAMETERS` and `ALL` screen both `from` and `to`. + +The workflow accepts at most ten unique addresses per evaluation. + +### Apply global and category thresholds + +TRM assigns an overall risk level to each address. ACE orders the levels as follows: + +```text +UNKNOWN < LOW < MEDIUM < HIGH < SEVERE +``` + +An address is rejected when its overall level meets or exceeds `risk_threshold`. For example, a `HIGH` threshold rejects `HIGH` and `SEVERE` results. + +You can also apply different thresholds to individual TRM risk categories. The following configuration rejects: + +- Any address with an overall risk level of `HIGH` or `SEVERE`. +- Any `Sanctions` indicator at `LOW` or above. +- Any `Darknet Market` indicator at `MEDIUM` or above. + +```json +{ + "risk_threshold": "HIGH", + "block_unknown": false, + "category_filters": [ + { "category": "Sanctions", "threshold": "LOW" }, + { "category": "Darknet Market", "threshold": "MEDIUM" } + ] +} +``` + +Category names are matched case-insensitively against the categories returned by TRM. Consult your TRM Wallet Screening account for the categories available to your organization. + + + +## Create the offchain policy + +Create the policy with `POST /v1/policies`. Use the same policy engine and chains as the target you plan to protect: + +```bash +curl -X POST https://ace.api.chain.link/v1/policies \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "policy_kind": "offchain", + "name": "Transaction wallet screening", + "type": "wallet_risk_scoring", + "policy_engine_id": "", + "onchain_policies": [ + { "chain_selector": "" } + ], + "config": { + "secret_name": "trmApiKey", + "addresses_to_check": "ALL", + "fail_mode": "CLOSED", + "risk_threshold": "HIGH", + "block_unknown": false, + "category_filters": [ + { "category": "Sanctions", "threshold": "LOW" }, + { "category": "Darknet Market", "threshold": "MEDIUM" } + ] + } + }' +``` + +ACE allows one active offchain policy per organization. Creating another returns a conflict until the existing policy is archived. + +Policy creation is asynchronous. The initial response includes the policy ID and a `deployment_status` such as `pending` or `deploying`. Poll the policy until it becomes `active`: + +```bash +curl https://ace.api.chain.link/v1/policies/ \ + -H "Authorization: Apikey " +``` + +ACE creates a managed CRE workflow and deploys one CADV contract per selected chain. When the policy becomes active, `action_validators` contains each chain selector and CADV address. + + + +## Attach the policy to a target function + +A protection connects the managed policy to a function on your target. The `extractor_output_ids` must identify, in order, the values that the permit will bind to onchain. + +For `transfer(address,uint256)`, use the `from`, `to`, and `amount` outputs from the same `ERC20TransferExtractor`: + +```bash +curl -X POST https://ace.api.chain.link/v1/targets//protections \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "policy_kind": "offchain", + "policy_instance_id": "", + "function_signature": "transfer(address,uint256)", + "desired_position": 0, + "extractor_output_ids": [ + "", + "", + "" + ], + "onchain_target_protections": [ + { "chain_selector": "" } + ] + }' +``` + +The selected chains must be a subset of the chains configured on the offchain policy. The target and policy must also belong to the same policy engine. + +Protection attachment is asynchronous and returns `202 Accepted`. Poll the policy's protections until the new protection becomes `active`: + +```bash +curl https://ace.api.chain.link/v1/policies//protections \ + -H "Authorization: Apikey " +``` + +Once active, calls to the protected function require a matching permit. Continue with [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits). + +## Update the policy configuration + +Updating the configuration redeploys the managed workflow but does not replace its CADV contracts or protections: + +```bash +curl -X PUT https://ace.api.chain.link/v1/policies//config \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "config": { + "secret_name": "trmApiKey", + "addresses_to_check": "ALL", + "fail_mode": "CLOSED", + "risk_threshold": "SEVERE", + "block_unknown": true, + "category_filters": [ + { "category": "Sanctions", "threshold": "LOW" } + ] + } + }' +``` + +The policy enters `config_updating` and returns to `active` after the workflow is redeployed. Do not request new evaluations while the configuration is updating. + +## Remove a protection or policy + +Remove a protection before archiving its policy: + +```bash +curl -X DELETE \ + https://ace.api.chain.link/v1/policies//protections/ \ + -H "Authorization: Apikey " +``` + +The removal is asynchronous. After all protections are removed, archive the policy: + +```bash +curl -X PATCH https://ace.api.chain.link/v1/policies/ \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "policy_kind": "offchain", + "status": "archived" + }' +``` + +Archiving removes the managed workflow and its event watchers. It also allows the organization to create a new offchain policy. + +## Beta and MVP limitations + +- `wallet_risk_scoring` is the only managed offchain policy type. +- Each organization can have one active offchain policy. +- Each evaluation can screen at most ten unique addresses. +- Every permit is single-use (`maxUses = 1`) and does not expire (`expiry = 0`). These values are not configurable in the current release. +- The values in `permit_parameters` must match the outputs configured on the protection and the values extracted from the eventual onchain call. +- General CRE service limits also apply. See [CRE Service Quotas](https://docs.chain.link/cre/service-quotas). + +## Related pages + +- [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) — integrate evaluations and permits into an application +- [Off-Chain Policy Execution](/ace/concepts/off-chain-policies) — conceptual overview of managed and custom offchain policies +- [CertifiedActionDONValidatorPolicy](/ace/reference/policy-library/certified-action-don-validator-policy) — onchain permit validation +- [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) — protection concepts and evaluation order +- [Coordinator API Reference](/api/ace/coordinator/docs) — policy and protection API schemas diff --git a/src/content/ace/guides/policy-manager/offchain-policies/request-offchain-permits.mdx b/src/content/ace/guides/policy-manager/offchain-policies/request-offchain-permits.mdx new file mode 100644 index 00000000000..c00ada3c1b6 --- /dev/null +++ b/src/content/ace/guides/policy-manager/offchain-policies/request-offchain-permits.mdx @@ -0,0 +1,249 @@ +--- +section: ace +title: "Requesting Offchain Permits" +date: Last Modified +metadata: + description: "Call the Chainlink ACE Evaluation API, monitor a TRM wallet risk evaluation, and use the resulting permit for a protected onchain transaction." + datePublished: "2026-07-17" + lastModified: "2026-07-17" +--- + +import { Aside } from "@components" + +After a managed offchain risk policy protects a function, the function rejects calls that do not have a matching permit. Your application must request an evaluation, wait for the permit to be stored onchain, and then submit the protected transaction. + +This guide uses an ERC-20 `transfer(address,uint256)` as the example. For policy and protection setup, see [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies). + + + +## Evaluation flow + +1. Your application describes the intended transaction to the ACE Evaluation API. +1. ACE triggers the managed CRE workflow for your organization. +1. The workflow extracts the configured addresses and screens them with TRM Wallet Screening. +1. If the risk rules reject any address, the evaluation becomes `rejected` and no permit is created. +1. If the risk rules pass, the workflow writes a permit to the CADV contract through the Keystone Forwarder. +1. After ACE observes the onchain `PermitStored` event, the evaluation becomes `ready`. +1. Your application submits the protected transaction with the same caller, target, function, and parameter values. +1. The policy engine finds and consumes the permit. The permit cannot authorize another transaction. + +## Prerequisites + +Before requesting an evaluation, verify that: + +- The offchain policy has `deployment_status: active`. +- Its protection for the target function has `status: active`. +- You know the target contract address and chain selector. +- You know the wallet that will submit the onchain transaction. It must be the same address as `caller_address`. +- You know the ordered extractor outputs configured on the protection. Your `permit_parameters` must use that same order. + +## Evaluation API + +The production Evaluation API base URL is: + +```text +https://ace.api.chain.link/v1/evaluation +``` + +It uses the same ACE API key as the Coordinator API: + +```http +Authorization: Apikey +``` + + + +## Construct the evaluation request + +Start an evaluation with `POST /evaluate`: + +```json +{ + "caller_address": "0x1111111111111111111111111111111111111111", + "subject": "0x2222222222222222222222222222222222222222", + "function_signature": "transfer(address,uint256)", + "parameters": { + "to": "0x3333333333333333333333333333333333333333", + "amount": "100" + }, + "permit_parameters": [ + "0x0000000000000000000000001111111111111111111111111111111111111111", + "0x0000000000000000000000003333333333333333333333333333333333333333", + "0x0000000000000000000000000000000000000000000000000000000000000064" + ], + "chain_selector": "", + "unique_evaluation_id": "transfer-018f6b3e-7c42-7a1f-a8ed-5ecf90c03b30" +} +``` + +|
Field
| Description | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | +| `caller_address` | Wallet that will submit the protected transaction. | +| `subject` | Address of the protected target contract. | +| `function_signature` | Canonical function signature, such as `transfer(address,uint256)`. Do not send the four-byte selector. | +| `parameters` | Structured representation of the function arguments. ACE stores it with the evaluation as contextual data. | +| `permit_parameters` | Ordered ABI-encoded values used for address screening and exact onchain permit matching. | +| `chain_selector` | Chain where the target, policy engine, protection, and CADV are deployed. | +| `unique_evaluation_id` | Client-generated identifier unique to this transaction intent. ACE uses it to derive the permit ID. | + +### Encode permit parameters + +Each `permit_parameters` item is a `0x`-prefixed, 32-byte ABI word. The items must have the same order as the `extractor_output_ids` on the protection. + +For `transfer(address,uint256)`, the `ERC20TransferExtractor` produces: + +```text +[from, to, amount] +``` + +Therefore, encode: + +1. `from`: the transaction caller, as an ABI `address`. +1. `to`: the transfer recipient, as an ABI `address`. +1. `amount`: the transfer amount, as an ABI `uint256`. + +Use a standard ABI library rather than concatenating untrusted values manually. For example, with ethers v6: + +```javascript +import { AbiCoder } from "ethers" + +const abiCoder = AbiCoder.defaultAbiCoder() + +const permitParameters = [ + abiCoder.encode(["address"], [callerAddress]), + abiCoder.encode(["address"], [recipientAddress]), + abiCoder.encode(["uint256"], [amount]), +] +``` + + + +### Choose a unique evaluation ID + +`unique_evaluation_id` is scoped to your ACE organization. ACE combines it with the organization ID to derive a deterministic `permit_id`. + +Retrying with the same `unique_evaluation_id` is idempotent: ACE returns the existing evaluation instead of triggering another workflow execution. Never reuse an ID for a different caller, target, function, or set of parameters. + +Use a UUID or another collision-resistant identifier generated by your backend. Store it with the transaction intent so you can safely recover from a lost HTTP response. + +## Start the evaluation + +```bash +curl -X POST https://ace.api.chain.link/v1/evaluation/evaluate \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d @evaluation.json +``` + +The response contains the deterministic permit ID and initial status: + +```json +{ + "permit_id": "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "status": "evaluating" +} +``` + +The response does not mean that the transaction is approved. Wait until the evaluation becomes `ready`. + +## Poll the evaluation + +Retrieve the evaluation using the returned permit ID: + +```bash +curl \ + https://ace.api.chain.link/v1/evaluation/evaluate/ \ + -H "Authorization: Apikey " +``` + +Polling every five seconds is a reasonable default. Stop when the evaluation reaches a terminal status. + +| Status | Terminal | Meaning | +| ------------ | -------- | ----------------------------------------------------------------------------------- | +| `evaluating` | No | The workflow is screening the configured addresses. | +| `approving` | No | TRM checks passed and the workflow is publishing the permit onchain. | +| `ready` | Yes | The permit was stored onchain. The protected transaction can now be submitted. | +| `rejected` | Yes | At least one configured risk rule rejected the evaluation. No permit was created. | +| `error` | Yes | The evaluation or onchain permit publication failed. No usable permit is available. | + +For `rejected` and `error`, the response can include a `reason`. `workflow_execution_id` identifies the CRE execution when available. Because permits do not expire in the current release, `expires_at` is normally `null`. + +```json +{ + "permit_id": "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "status": "ready", + "reason": null, + "workflow_execution_id": "", + "expires_at": null +} +``` + +## Retry an evaluation + +The CRE HTTP trigger allows one new execution per workflow every 60 seconds. Polling an existing evaluation does not trigger the workflow and is not subject to that trigger rate. + +- If the initial HTTP response is lost or ambiguous, retry `POST /evaluate` with the same `unique_evaluation_id`. ACE returns the existing evaluation if it was created. +- If an evaluation reaches `rejected`, changing the identifier alone does not change the policy decision. Review the risk result or transaction intent. +- If an evaluation reaches `error` and the underlying issue is resolved, wait at least 60 seconds and submit a new evaluation with a new `unique_evaluation_id`. + +See [CRE Service Quotas](https://docs.chain.link/cre/service-quotas) for current workflow limits. + +## Submit the protected transaction + +Submit the transaction only after the evaluation becomes `ready`. The sender must be `caller_address`, and the target function must receive values that produce the same extracted parameters as `permit_parameters`. + +No permit bytes are added to the transaction. The CADV already stores the permit and looks it up from the action's caller, target, selector, and extracted parameters. + +After the protected call succeeds, the CADV increments the permit's usage counter. Managed risk policy permits have `maxUses = 1`, so another transaction with the same intent requires a new evaluation and permit. + + + +## Troubleshooting + +### Evaluation is rejected + +- At least one address met or exceeded the global `risk_threshold`. +- A TRM risk indicator met or exceeded a configured category threshold. +- TRM returned `UNKNOWN` and `block_unknown` is enabled. + +Review the response `reason` and the policy configuration. Do not retry a rejected intent without understanding why it was rejected. + +### Evaluation returns an error + +- The Vault DON secret identifier does not match `secret_name`. +- The TRM credential was not encoded as `:` before Base64 encoding. +- TRM or the CRE confidential HTTP request failed. +- The selected chain does not have an active CADV for the policy. +- The workflow could not write the permit onchain. + +If a TRM HTTP error should allow the action, review the policy's `fail_mode`. Use `OPEN` only after assessing the compliance impact. + +### Evaluation is ready but the transaction reverts + +- The transaction sender differs from `caller_address`. +- The target address or function differs from the evaluation. +- The eventual transaction produces different extractor values than `permit_parameters`. +- The protection's extractor outputs are missing or ordered differently. +- The permit has already been consumed. +- Another policy in the target function's policy chain rejected the call. + +## Related pages + +- [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) — configure TRM screening and attach protections +- [Granting Evaluation Access](/ace/guides/policy-manager/offchain-policies/grant-evaluation-access) — let another organization request evaluations against your target +- [Off-Chain Policy Execution](/ace/concepts/off-chain-policies) — conceptual overview +- [CertifiedActionDONValidatorPolicy](/ace/reference/policy-library/certified-action-don-validator-policy) — how permits are stored and consumed onchain +- [Evaluation API Reference](/api/ace/evaluation/docs) — complete request, response, and error schemas +- [CRE Service Quotas](https://docs.chain.link/cre/service-quotas) — current CRE workflow limits diff --git a/src/content/ace/index.mdx b/src/content/ace/index.mdx index 2aace1ea705..d4666f521b7 100644 --- a/src/content/ace/index.mdx +++ b/src/content/ace/index.mdx @@ -5,16 +5,14 @@ isIndex: true date: Last Modified metadata: description: "Chainlink ACE (Automated Compliance Engine) provides programmable, cross-chain compliance for smart contracts. Learn how ACE enables modular policy management and cross-chain identity verification." - datePublished: "2026-04-20" - lastModified: "2026-08-18" + datePublished: "2026-03-31" + lastModified: "2026-07-17" --- -import { Aside, ClickToZoom } from "@components" +import { ClickToZoom } from "@components" +import AceCallout from "@features/ace/callouts/AceCallout.astro" - + **Chainlink Automated Compliance Engine (ACE)** is a compliance layer for EVM smart contracts. It enforces rules — transfer limits, identity checks, sanctions screening, and more — at transaction time, without embedding compliance logic in your application code. Rules can be added, updated, or removed through the ACE Platform — your application contract doesn't need to change. @@ -102,9 +100,8 @@ Start here regardless of your role: - **[Policy Manager](/ace/concepts/policy-management)** — Configure and manage compliance policies for your smart contracts. - **[Identity Manager](/ace/concepts/cross-chain-identity)** — Manage cross-chain identities and credentials. +- **[API Reference](/ace/reference/apis)** — Use the Coordinator, Evaluation, and Reporting APIs programmatically. - **[Policy Library](/ace/reference/policy-library)** — All pre-built policies with configuration details. - **[Policy Ordering & Composition](/ace/concepts/policy-ordering)** — How to compose effective rulesets. -- **[Coordinator API](/ace/reference/api/coordinator)** — Manage ACE resources programmatically. -- **[Evaluation API](/ace/reference/api/evaluation)** — Request managed offchain policy evaluations and permits. -- **[Reporting API](/ace/reference/api/reporting)** — Query compliance state and transaction history. +- **[Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible)** — Integration guide for developers adding ACE to new or existing smart contracts. - **[Signing & Ownership Model](/ace/concepts/signing-ownership)** — Understand delegated and self-signing models. diff --git a/src/content/ace/llms-full.txt b/src/content/ace/llms-full.txt index 0e97c61ff5c..1885ca8cf0d 100644 --- a/src/content/ace/llms-full.txt +++ b/src/content/ace/llms-full.txt @@ -1,11 +1,8 @@ # Chainlink ACE Overview Source: https://docs.chain.link/ace -Last Updated: 2026-08-18 +Last Updated: 2026-07-17 - + **Chainlink Automated Compliance Engine (ACE)** is a compliance layer for EVM smart contracts. It enforces rules — transfer limits, identity checks, sanctions screening, and more — at transaction time, without embedding compliance logic in your application code. Rules can be added, updated, or removed through the ACE Platform — your application contract doesn't need to change. @@ -88,23 +85,19 @@ Start here regardless of your role: - **[Policy Manager](/ace/concepts/policy-management)** — Configure and manage compliance policies for your smart contracts. - **[Identity Manager](/ace/concepts/cross-chain-identity)** — Manage cross-chain identities and credentials. +- **[API Reference](/ace/reference/apis)** — Use the Coordinator, Evaluation, and Reporting APIs programmatically. - **[Policy Library](/ace/reference/policy-library)** — All pre-built policies with configuration details. - **[Policy Ordering & Composition](/ace/concepts/policy-ordering)** — How to compose effective rulesets. -- **[Coordinator API](/ace/reference/api/coordinator)** — Manage ACE resources programmatically. -- **[Evaluation API](/ace/reference/api/evaluation)** — Request managed offchain policy evaluations and permits. -- **[Reporting API](/ace/reference/api/reporting)** — Query compliance state and transaction history. +- **[Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible)** — Integration guide for developers adding ACE to new or existing smart contracts. - **[Signing & Ownership Model](/ace/concepts/signing-ownership)** — Understand delegated and self-signing models. --- # Beta Scope Source: https://docs.chain.link/ace/beta-scope -Last Updated: 2026-08-18 +Last Updated: 2026-07-17 - + ACE Beta is an early-access release for testing and integration on supported mainnet and testnet networks. The limitations listed below are all areas of active development. Each will be addressed as ACE progresses toward general availability. @@ -114,10 +107,10 @@ ACE Beta is available on selected [mainnet and testnet networks](/ace/supported- ## No custom extractors or mappers -ACE Beta provides a library of [pre-built, audited policies](/ace/reference/policy-library) (allowlists, volume limits, role-based access control, pause controls, and more). You can also register your own [custom policies](/ace/concepts/policy-management). The following customizations are **not available** through the platform during Beta: +ACE Beta provides a library of [pre-built, audited policies](/ace/reference/policy-library) (allowlists, volume limits, role-based access control, pause controls, and more). You can also register your own [custom policies](/ace/guides/policy-manager/custom-policies). The following customizations are **not available** through the platform during Beta: -- **Custom extractors**: Writing extractors for function signatures beyond the pre-built ERC-20 and ERC-3643 set. -- **Custom mappers**: Deploying mapper contracts that transform or combine extracted parameters before they reach a policy. +- **Custom extractors** — Writing extractors for function signatures beyond the pre-built ERC-20 and ERC-3643 set. +- **Custom mappers** — Deploying mapper contracts that transform or combine extracted parameters before they reach a policy. Because custom extractors are not available, the platform supports **ERC-20 and ERC-3643** function signatures only. These are the only contract types that get the full managed experience — policy configuration, reporting, and monitoring — through the Platform UI and Coordinator API. If you are building a different type of contract (vault, DEX, lending protocol), making it ACE-compatible could require custom extractors. Support for additional contract types and custom extractors is on the roadmap. @@ -137,19 +130,34 @@ In addition to attestation-based checks (verifying whether a credential exists), Data Validators are a **curated catalog maintained by Chainlink**, not something organizations deploy themselves. This is **by design**: curating the available validators and their data schemas ensures no personally identifiable information (PII) are used. The first available validator supports **jurisdiction control** using [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes; Chainlink may add further validators over time. Bringing your own Data Validator is not offered — this is a permanent design choice, not a Beta limitation. -For the conceptual explanation of attestation-only vs. Credential Data Validator checks, see [Cross-Chain Identity — Credential data and privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy). +To get started, see [Managing Data Validators](/ace/guides/policy-manager/manage-data-validators). For the conceptual explanation of attestation-only vs. Credential Data Validator checks, see [Cross-Chain Identity — Credential data and privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy). ## Signing model is chosen at onboarding ACE supports two signing models: **delegated signing** (Chainlink signs and executes transactions on your behalf) and **self-signing** (you sign operations yourself using the [CRE Connect SDK](https://github.com/smartcontractkit/crec-sdk)). Your organization chooses its signing model during onboarding. -In both models, you retain full ownership of your contracts through the [CRE Connect Wallet](/ace/concepts/key-terms#cre-connect-wallet). See [Signing & Ownership Model](/ace/concepts/signing-ownership) for details on how each model works. +In both models, you retain full ownership of your contracts through the [CRE Connect Wallet](/ace/concepts/signing-ownership). See [Signing & Ownership Model](/ace/concepts/signing-ownership) for details on how each model works. ## Managed offchain risk policies are limited during Beta -ACE Beta provides a managed policy that screens transaction participants with TRM Wallet Screening and delivers approved permits onchain through a managed CRE workflow. +ACE Beta provides a managed `wallet_risk_scoring` policy that screens transaction participants with TRM Wallet Screening and delivers approved permits onchain through a managed CRE workflow. + + + +The following limitations apply: + +- **TRM access required** — Your organization must have a TRM Labs account with Wallet Screening API access and provide its own API credential through CRE Vault DON. +- **One policy type** — `wallet_risk_scoring` is the only managed offchain policy available. Custom offchain integrations require assistance from Chainlink. +- **One active policy per organization** — Archive the existing offchain policy before creating another. +- **Ten addresses per evaluation** — A workflow execution can screen at most ten unique wallet addresses. +- **Fixed permit lifetime and usage** — Managed permits are single-use and do not expire. These values are not configurable in the current release. +- **Extractor-dependent protection** — Permit parameters must correspond to supported extractor outputs and exactly match the values extracted from the eventual onchain transaction. +- **CRE quotas apply** — Evaluations are subject to current [CRE Service Quotas](https://docs.chain.link/cre/service-quotas), including the HTTP trigger rate limit. -See [Off-Chain Policy Execution](/ace/concepts/off-chain-policies) for a conceptual overview of managed and custom offchain policies. +See [Offchain Policies](/ace/guides/policy-manager/offchain-policies) for an overview, [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) to configure wallet screening, and [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) to integrate evaluations into an application. --- @@ -157,8 +165,15 @@ See [Off-Chain Policy Execution](/ace/concepts/off-chain-policies) for a concept Source: https://docs.chain.link/ace/supported-networks Last Updated: 2026-05-26 + + ACE Beta is available on the following networks. + + ## Mainnet networks ## Testnet networks @@ -173,24 +188,32 @@ ACE Beta is available on the following networks. # Release Notes Source: https://docs.chain.link/ace/release-notes -Last Updated: 2026-08-18 +Last Updated: 2026-07-17 + + ## July 17, 2026 — ACE Beta+ -ACE Beta+ builds on ACE Beta with new compliance capabilities. This release is still a Beta; see [Beta Scope](/ace/beta-scope) for the current scope and limitations. +ACE Beta+ builds on ACE Beta with new compliance capabilities. This release is still a Beta — see [Beta Scope](/ace/beta-scope) for the current scope and limitations. ### What's new -- **Custom policies**: You can now write, deploy, and register your own policy contract and use it like a pre-built library policy. See [Custom policy](/ace/concepts/key-terms#custom-policy). -- **Grouped identity validation**: The new [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) applies different credential requirements to different accounts. It routes each account to a group, by credential attestation or by credential data (for example, jurisdiction), then validates the account against that group's requirements. Use it to enforce, for example, one rule set for individuals and another for businesses, or different rules per jurisdiction, within a single policy. -- **External registries**: Organizations can now share registries with each other. A registry owner grants another organization read access, and the grantee can use the shared registry's identities and credentials as a credential source in its own policies without re-issuing credentials. Access is read-only for the recipient and revocable at any time. See [Cross-Chain Identity](/ace/concepts/cross-chain-identity#sharing-registries-across-organizations). -- **Credential data validation**: Credentials are no longer limited to attestation-only. You can now link a data schema to a credential type, issue credentials that carry structured data, and attach a [Data Validator](/ace/concepts/cross-chain-identity#credential-data-and-privacy) to a policy's credential source to enforce rules on that data. The first use case is **jurisdiction control** using a pre-built AllowDenyList Data Validator with [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes. Data Validators are a curated catalog maintained by Chainlink. -- **Self-signing model**: Organizations can now choose between **delegated signing** (Chainlink signs on your behalf) and **self-signing** (you sign operations yourself) at onboarding. With self-signing, ACE creates unsigned draft operations that you poll, sign (EIP-712), and submit using the [CRE Connect SDK](https://github.com/smartcontractkit/crec-sdk). Both models use the same CRE Connect Wallet and all platform capabilities work identically. See [Signing & Ownership Model](/ace/concepts/signing-ownership). -- **Managed offchain risk policies**: Screen transaction participants with [TRM Wallet Screening](https://www.trmlabs.com/blockchain-intelligence-platform/wallet-screening) before allowing a protected onchain action. Configure global and category-specific risk thresholds, request evaluations through the new [Evaluation API](/ace/reference/api/evaluation), and receive single-use permits delivered onchain by a managed CRE workflow. See [Off-Chain Policy Execution](/ace/concepts/off-chain-policies). +- **Custom policies** — You can now write, deploy, and register your own policy contract and use it like a pre-built library policy. Custom policy implementations are scoped to your organization. See [Custom Policies](/ace/guides/policy-manager/custom-policies). +- **Grouped identity validation** — The new [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) applies different credential requirements to different accounts. It routes each account to a group — by credential attestation or by credential data (for example, jurisdiction) — then validates the account against that group's requirements. Use it to enforce, for example, one rule set for individuals and another for businesses, or different rules per jurisdiction, within a single policy. +- **External registries** — Organizations can now share registries with each other. A registry owner grants another organization read access, and the grantee can use the shared registry's identities and credentials as a credential source in its own policies — without re-issuing credentials. Access is read-only for the recipient and revocable at any time. See [External Registries](/ace/guides/identity-manager/external-registries). +- **Credential data validation** — Credentials are no longer limited to attestation-only. You can now link a [data schema](/ace/guides/identity-manager/manage-credential-types#typed-credentials-with-data-schemas) to a credential type, issue credentials that carry structured data, and attach a [Data Validator](/ace/guides/policy-manager/manage-data-validators) to a policy's credential source to enforce rules on that data. The first use case is **jurisdiction control** using a pre-built AllowDenyList Data Validator with [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes. See [Credential data and privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy). +- **Self-signing model** — Organizations can now choose between **delegated signing** (Chainlink signs on your behalf) and **self-signing** (you sign operations yourself) at onboarding. With self-signing, ACE creates unsigned draft operations that you poll, sign (EIP-712), and submit using the [CRE Connect SDK](https://github.com/smartcontractkit/crec-sdk). Both models use the same CRE Connect Wallet and all platform capabilities work identically. See [Signing & Ownership Model](/ace/concepts/signing-ownership). +- **Managed offchain risk policies (MVP)** — Screen transaction participants with [TRM Wallet Screening](https://www.trmlabs.com/blockchain-intelligence-platform/wallet-screening) before allowing a protected onchain action. Configure global and category-specific risk thresholds, request evaluations through the new Evaluation API, and receive single-use permits delivered onchain by a managed CRE workflow. Grant evaluation access to other organizations so they can request permits against your target contracts. This feature is an MVP; contact your Chainlink representative for help with setup. See [Offchain Policies](/ace/guides/policy-manager/offchain-policies), [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies), [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits), and [Granting Evaluation Access](/ace/guides/policy-manager/offchain-policies/grant-evaluation-access). ## May 26, 2026 — Mainnet support -ACE Beta is now live on the following mainnet networks: Ethereum, Arbitrum, Avalanche, Base, and Polygon, in addition to existing testnets. See [Supported Networks](/ace/supported-networks) for chain IDs and chain selectors. +### What's new + +- **Mainnet deployments** — ACE Beta now supports mainnet on Ethereum, Arbitrum, Avalanche, Base, and Polygon, in addition to existing testnets. Mainnet access is not enabled for all organizations by default — contact your Chainlink contact to request it. See [Supported Networks](/ace/supported-networks) for chain IDs and chain selectors. + +### Other improvements + +- Policy Engine creation is now available in the Platform UI under **Compliance > Policy Manager** (previously API-only). See [Managing Policy Engines](/ace/guides/policy-manager/manage-engines#create-a-policy-engine). ## April 15, 2026 — ACE Beta (Private Release) @@ -200,9 +223,9 @@ Chainlink ACE Beta is now available to a first set of selected participants as a ACE Beta ships with three core components: -- **Policy Manager**: Create policy engines, register target contracts, configure policy instances from the pre-built library, and enforce compliance rules on-chain. -- **Identity Manager**: Set up identity registries, register on-chain identities, define credential types, and issue verifiable credentials for use in identity-based policies. -- **Reporting Manager**: Query on-chain policy configurations, identity states, and transaction history via a read-only API to support compliance verification and auditing workflows. +- **Policy Manager** — Create policy engines, register target contracts, configure policy instances from the pre-built library, and enforce compliance rules on-chain. +- **Identity Manager** — Set up identity registries, register on-chain identities, define credential types, and issue verifiable credentials for use in identity-based policies. +- **Reporting Manager** — Query on-chain policy configurations, identity states, and transaction history via a read-only API to support compliance verification and auditing workflows. All three components support multi-chain deployments across all [supported testnets](/ace/supported-networks) and are accessible via the [Chainlink Platform UI](https://app.chain.link) and the Coordinator and Reporting APIs, once Chainlink provisions your organization with ACE Beta access. @@ -214,7 +237,9 @@ This is a testnet-only release. For full details on what is supported, known con # ACE Architecture Source: https://docs.chain.link/ace/concepts/architecture -Last Updated: 2026-04-20 +Last Updated: 2026-03-31 + + ACE has two layers: **onchain smart contracts** that enforce compliance rules on the blockchain, and the **[ACE Platform](/ace/concepts/key-terms#ace-platform)** (UI and APIs) that lets you manage them. Under the hood, Chainlink infrastructure connects the two — routing your platform actions to the blockchain and indexing onchain events back into the Reporting API. This page gives a bird's-eye view of how all the pieces fit together. @@ -224,7 +249,7 @@ The following diagram shows the complete ACE architecture, from the ACE Platform The **ACE Platform** is everything you interact with: the **Platform UI**, the **Coordinator API** (to manage ACE resources), and the **Reporting API** (to query what happened onchain). The UI calls the Coordinator API under the hood, so both paths converge. -When you manage ACE (create policies, register identities, etc.), the Coordinator API routes your request through **CRE Connect**, which executes the blockchain transaction via your organization's **CRE Connect Wallet**. The CRE Connect Wallet owns all your ACE contracts and verifies that only authorized operators can act on them. +When you manage ACE (create policies, register identities, etc.), the Coordinator API routes your request through **CRE Connect**, which executes the blockchain transaction via your organization's **[CRE Connect Wallet](/ace/concepts/signing-ownership)**. The CRE Connect Wallet owns all your ACE contracts and verifies that only authorized operators can act on them. In the other direction, when policies run onchain, the contracts emit events. **Chainlink's indexing infrastructure** continuously monitors these events, indexes the data, and makes it available through the **Reporting API** — giving you a queryable view of all policy run activity, transaction history, and onchain state. @@ -238,19 +263,19 @@ ACE Beta provides three managers that abstract away the complexity of managing o The Policy Manager lets you create, configure, and deploy onchain compliance rules for your smart contracts. You can browse available policy types (allowlist, volume limits, role-based access control, etc.), create policy instances with per-network configuration, and attach them to specific function selectors on your protected contracts. -The Policy Manager operates on the **Policy Management** onchain contracts: it deploys and configures PolicyEngine instances, policy contracts, and extractors on your behalf. See the [Coordinator API](/ace/reference/api/coordinator) to get started. +The Policy Manager operates on the **Policy Management** onchain contracts: it deploys and configures PolicyEngine instances, policy contracts, and extractors on your behalf. See the [Policy Manager guides](/ace/guides/policy-manager/manage-engines) or the [Coordinator API reference](/api/ace/coordinator/docs) to get started. ### Identity Manager The Identity Manager lets you manage cross-chain identities and credentials. You can create identity and credential registries, register wallet addresses to CCIDs, define credential types, and issue credentials to users. -The Identity Manager operates on the **Cross-Chain Identity** onchain contracts: it writes to IdentityRegistry and CredentialRegistry instances on your behalf. See the [Coordinator API](/ace/reference/api/coordinator) to get started. +The Identity Manager operates on the **Cross-Chain Identity** onchain contracts: it writes to IdentityRegistry and CredentialRegistry instances on your behalf. See the [Identity Manager guides](/ace/guides/identity-manager/manage-identities) or the [Coordinator API reference](/api/ace/coordinator/docs) to get started. ### Reporting Manager The Reporting Manager gives you read-only access to onchain state and transaction history. You can query policy engines and their configurations, look up identities and credentials, and view policy run transactions with filtering by network, target contract, and time range. -The Reporting Manager exposes data through the **Reporting API**. Under the hood, Chainlink's indexing infrastructure monitors onchain events (such as `PolicyRunComplete`) emitted by your PolicyEngines and indexes the data so it can be queried through the API. See the [Reporting API overview](/ace/reference/api/reporting) for details. +The Reporting Manager exposes data through the **Reporting API**. Under the hood, Chainlink's indexing infrastructure monitors onchain events (such as `PolicyRunComplete`) emitted by your PolicyEngines and indexes the data so it can be queried through the API. See the [API Overview](/ace/reference/apis) for available endpoints. -To view or change the default result for a target contract, use the ACE Platform UI or Coordinator API. +To view or change the default result for a target contract, see [Managing Targets — Default allow behavior](/ace/guides/policy-manager/manage-targets#default-allow-behavior). ## Managing the policy chain @@ -872,13 +977,13 @@ Each policy in the chain costs gas. While the PolicyEngine is designed for effic Any time you add, remove, or reorder a policy, review the full chain to confirm the new ordering matches your intent. A single misplaced policy can create a gap in your compliance coverage. -For additional security guidance around trust boundaries, external call risks, and context handling, consult your integration documentation. +For additional security guidance around trust boundaries, external call risks, and context handling, see [Security Considerations](/ace/guides/policy-manager/contracts/security-considerations). --- # Cross-Chain Identity Source: https://docs.chain.link/ace/concepts/cross-chain-identity -Last Updated: 2026-08-18 +Last Updated: 2026-07-17 This page explains in depth how ACE's Cross-Chain Identity system works — the CCID model, the credential lifecycle, and how applications validate identities at runtime. For a high-level overview of the components themselves (IdentityRegistry, CredentialRegistry, CredentialRegistryIdentityValidatorPolicy), see the [Architecture page](/ace/concepts/architecture#cross-chain-identity-contracts). @@ -934,7 +1039,7 @@ Both registries are themselves protected by a PolicyEngine. This means write acc ### Sharing registries across organizations -A registry owner can grant another organization **read access** to a registry, so that organization can reference the same identities and credentials in its own policies without re-issuing them. Access is read-only for the recipient and revocable at any time. This is how a protocol reuses a provider's registry directly. See [External registry](/ace/concepts/key-terms#external-registry) in the glossary. +A registry owner can grant another organization **read access** to a registry, so that organization can reference the same identities and credentials in its own policies without re-issuing them. Access is read-only for the recipient and revocable at any time. This is how a protocol reuses a provider's registry directly. See [External Registries](/ace/guides/identity-manager/external-registries). ## Credential type identifiers @@ -1002,7 +1107,7 @@ The system supports two levels of credential verification: **Attestation-only** is the default mode. The policy asks a single binary question: *does this credential exist for this identity?* The answer is yes or no. The `credentialData` field is ignored entirely. This is sufficient for many compliance scenarios — for example, verifying that a user holds a `common.kyc` credential before allowing a transfer. -**Credential Data Validator** adds a second layer. When a `ICredentialDataValidator` contract is configured on a Credential Source, the policy first checks that the credential exists, then passes the `credentialData` bytes to the validator contract for an additional custom check. The validator reads the non-PII data and returns `true` or `false`. ACE provides a pre-built **AllowDenyList Data Validator** whose first use case is jurisdiction control using [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes. +**Credential Data Validator** adds a second layer. When a `ICredentialDataValidator` contract is configured on a Credential Source, the policy first checks that the credential exists, then passes the `credentialData` bytes to the validator contract for an additional custom check. The validator reads the non-PII data and returns `true` or `false`. ACE provides a pre-built **AllowDenyList Data Validator** whose first use case is jurisdiction control using [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes. See [Managing Data Validators](/ace/guides/policy-manager/manage-data-validators) to configure one. This enables decisions based on *what's inside* the credential, not just whether it exists. For example: @@ -1018,10 +1123,11 @@ This enables decisions based on *what's inside* the credential, not just whether ## Credential lifecycle @@ -1082,7 +1188,7 @@ Separate registries and validators allow: # Reporting Manager Source: https://docs.chain.link/ace/concepts/reporting -Last Updated: 2026-04-20 +Last Updated: 2026-03-31 This page explains what the Reporting Manager provides, why it matters for compliance, and how the data pipeline works. For a high-level overview of how the Reporting Manager fits into the ACE architecture, see the [Architecture page](/ace/concepts/architecture#reporting-manager). @@ -1138,21 +1244,23 @@ This gives you a complete snapshot of "what compliance rules protect this contra ### Identities -Look up identity records by wallet address, identity registry, credential type, credential registry, or CCID. Each identity includes all registry memberships (which registries, which chains, which wallet addresses are mapped). You can optionally request full credential details, including the credential type identifier, the issuing credential registry, issuance and expiration timestamps, credential data, and full on-chain provenance (block number, transaction hash). +Look up identity records by wallet address, identity registry, credential type, credential registry, or CCID. Each identity includes all registry memberships (which registries, which chains, which wallet addresses are mapped). + +Set `include_credential_details=true` to include each identity's credentials in the response. Each credential record contains the credential type identifier, the issuing credential registry, issuance and expiration timestamps, credential data, and full on-chain provenance (block number, transaction hash). This lets you answer questions like "does this wallet address belong to a registered identity?" or "which identities have a KYC credential issued by this credential registry?" ## Point-in-time queries -Policies, Identities, and Targets all support point-in-time historical queries. This lets you reconstruct the state of your compliance system at any historical moment: +The Policies, Identities, and Targets endpoints each accept a required **`as_of`** timestamp parameter. This lets you reconstruct the state of your compliance system at any historical moment: - **What policies were active** on a contract on a specific date? - **What credentials** did an identity hold at the time of a transaction? - **What protections** were configured on a target contract last quarter? -Point-in-time queries are critical for regulatory investigations where you need to prove not just that compliance rules exist *today*, but that they were in place *when a specific event occurred*. The API returns the version of each resource that was effective at the specified time, including resources that have since been updated or removed. +Point-in-time queries are critical for regulatory investigations where you need to prove not just that compliance rules exist *today*, but that they were in place *when a specific event occurred*. The `as_of` parameter returns the version of each resource that was effective at the specified time, including resources that have since been updated or removed. -Transactions support time range filtering instead, since transactions are discrete events rather than stateful resources. +The Transactions endpoint uses `from` and `to` time range filters instead, since transactions are discrete events rather than stateful resources. ## How data flows @@ -1177,29 +1285,35 @@ This happens automatically for all contracts deployed through the ACE Platform. During Beta, the Reporting Manager is **API-only** — there is no reporting UI. You interact with it exclusively through the Reporting API. -The API provides read-only access. All compliance configuration changes (deploying policies, registering identities, issuing credentials) are done through the [Coordinator API](/ace/reference/api/coordinator) or Platform UI. +The API provides read-only access. All compliance configuration changes (deploying policies, registering identities, issuing credentials) are done through the Coordinator API or Platform UI. ## Next steps -- **[Reporting API overview](/ace/reference/api/reporting)** — What the Reporting API provides and what resources can be queried. +- **[Interactive API Reference](/api/ace/reporting/docs)** — Try API calls directly in the browser with full request/response schemas. - **[Architecture](/ace/concepts/architecture#how-ace-observes-onchain-activity-read-path)** — How the indexing pipeline connects onchain events to the Reporting API. --- # Off-Chain Policy Execution Source: https://docs.chain.link/ace/concepts/off-chain-policies -Last Updated: 2026-08-18 +Last Updated: 2026-07-17 ACE policies are not limited to on-chain logic. Off-chain policy execution lets you enforce compliance rules that depend on data or systems outside the blockchain — your internal compliance engine, third-party risk APIs, or any custom business logic. The checks happen off-chain before the transaction, and the result is delivered on-chain as a cryptographic **permit** that authorizes the action. ACE provides two ways to use offchain policies: -- **Managed wallet risk screening** — An out-of-the-box policy that screens wallet addresses with TRM Wallet Screening. You configure the risk thresholds and call the ACE Evaluation API; Chainlink manages the CRE workflow and onchain permit delivery. +- **Managed wallet risk screening (MVP)** — An out-of-the-box policy that screens wallet addresses with TRM Wallet Screening. You configure the risk thresholds and call the ACE Evaluation API; Chainlink manages the CRE workflow and onchain permit delivery. - **Custom offchain integrations** — An advanced model for internal compliance systems, custom policy endpoints, and other business logic. These integrations require infrastructure hosted by your organization and assistance from Chainlink during Beta. + + ## Why off-chain policies? @@ -1220,7 +1334,7 @@ A protected function that requires off-chain verification will revert if the cal 2. ACE triggers a managed CRE workflow. The workflow selects the configured addresses and calls TRM Wallet Screening using the credential your organization stores in Vault DON. 3. The workflow compares TRM's results with the policy's global risk threshold, unknown-risk setting, and category-specific thresholds. 4. If an address is rejected, the evaluation becomes `rejected` and no permit is created. -5. If the evaluation passes, the workflow delivers a permit through the Keystone Forwarder. The [CertifiedActionDONValidatorPolicy](/ace/reference/policy-library/certified-action-don-validator-policy) (CADV) stores it onchain. +5. If the evaluation passes, the workflow delivers a permit through the Keystone Forwarder. The [CertifiedActionDONValidatorPolicy](/ace/reference/policy-library/certified-action-don-validator-policy) stores it onchain. 6. Your application polls the Evaluation API. When the status becomes `ready`, it submits the protected transaction. 7. The onchain policy matches the action to the stored permit and consumes it after the call succeeds. @@ -1259,7 +1373,8 @@ From the policy engine's perspective, the CertifiedActionDONValidatorPolicy is j ## Related pages -- [Evaluation API](/ace/reference/api/evaluation) — the runtime API for requesting managed offchain permits +- [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) — configure managed TRM wallet screening +- [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) — integrate the Evaluation API into an application - [Policy Management](/ace/concepts/policy-management) — how policy chains and evaluation work - [Architecture](/ace/concepts/architecture) — how the on-chain and off-chain layers connect - [CertifiedActionDONValidatorPolicy](/ace/reference/policy-library/certified-action-don-validator-policy) — the on-chain contract reference @@ -1269,326 +1384,3862 @@ From the policy engine's perspective, the CertifiedActionDONValidatorPolicy is j # Getting Started with ACE Source: https://docs.chain.link/ace/getting-started +Last Updated: 2026-04-15 -## Get Access to ACE + -Chainlink ACE is currently in **private beta**. To request access, schedule a demo, or learn more, [contact us](https://chain.link/contact). +ACE offers three managers. Choose the path that matches what you need to do: -Once access is granted, you will be able to: +| Manager | What it does | Get started | +| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| **Policy Manager** | Attach and configure compliance policies on smart contracts — volume limits, allowlists, RBAC, identity-based checks, and more | [Policy Manager Quick Start](/ace/getting-started/policy-manager) | +| **Identity Manager** | Manage identity registries, register cross-chain identities (CCIDs), and issue credentials such as Proof of Identity or accreditation attestations | [Identity Manager Quick Start](/ace/getting-started/identity-manager) | +| **Reporting Manager** | Query on-chain transaction history, policy configurations, and identity states via a read-only API for compliance verification | [Reporting concepts](/ace/concepts/reporting) — API Reference | -- Configure programmable compliance policies on your smart contracts using the **Policy Manager** -- Manage cross-chain identities and credentials using the **Identity Manager** -- Query onchain compliance state and transaction history using the **Reporting Manager** +### Shared first step -For an overview of ACE and its capabilities, see the [Overview](/ace) page and explore the [Core Concepts](/ace/concepts/architecture) section. +All three managers require **account setup** first — creating your organization, sharing your Org ID for enablement, generating an API key, and setting up [CRE Connect Wallets](/ace/concepts/key-terms#cre-connect-wallet): ---- +- [Account Setup](/ace/getting-started/account-setup) — the four steps every ACE user completes before using any manager -# Policy Management Contracts -Source: https://docs.chain.link/ace/reference/policy-management-contracts -Last Updated: 2026-04-20 +### Not sure which Manager you need? - +- If you are a **token issuer or protocol team** deciding which compliance rules to enforce, start with the [Policy Manager Quick Start](/ace/getting-started/policy-manager). +- If you are an **identity provider (IDV)** or **credential issuer**, or **sanctions data provider** supplying data for others to consume, start with the [Identity Manager Quick Start](/ace/getting-started/identity-manager). +- Many organizations use **both managers**. Start with whichever is most relevant to your first use case — the guides cross-reference each other where the workflows intersect. -The Policy Management contracts handle on-chain policy enforcement for ACE-compatible contracts. The source code and full documentation are available in the policy-management package of the chainlink-ace repository (Business Source License 1.1). +### Background reading -## Core interfaces +Before diving in, these pages provide essential context: -| Interface | Description | -| :--------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| [IPolicyEngine](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/interfaces/IPolicyEngine.sol) | Central orchestrator that manages policies, extractors, and mappers for protected contracts. Receives calls from `PolicyProtected` targets, runs the policy chain, and returns allow/reject decisions. | -| [IPolicyProtected](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/interfaces/IPolicyProtected.sol) | Base interface for any contract that wants policy enforcement. Provides the `runPolicy` modifier, the connection to a `PolicyEngine`, and context handling for passing off-chain data to policies. | -| [IPolicy](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/interfaces/IPolicy.sol) | Standard interface for all policy contracts. Each policy implements `run` (read-only evaluation that returns allow/continue/reject) and optionally `postRun` (state changes after execution, such as updating volume counters). | -| [IExtractor](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/interfaces/IExtractor.sol) | Parses transaction calldata into named parameters (e.g., `to` and `value` from an ERC-20 `transfer`) so policies can evaluate them. One extractor is registered per function signature. | -| [IMapper](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/interfaces/IMapper.sol) | Optional interface for transforming or combining extracted parameters before they reach a policy. Only needed for advanced scenarios where a policy expects a different parameter shape than the extractor provides. | +- [Signing & Ownership Model](/ace/concepts/signing-ownership) — how ACE manages keys and the delegated and self-signing models +- [ACE Architecture](/ace/concepts/architecture) — system components and how they connect +- [Key Terms](/ace/concepts/key-terms) — ACE-specific terminology +- [Beta Scope](/ace/beta-scope) — what is and is not available during the Beta program -## Pre-built policies +--- -ACE provides a library of audited, ready-to-use policy implementations covering common compliance scenarios — allowlists, volume limits, role-based access, pause controls, and more. See the policies source code for implementation details, or the [Policy Library](/ace/reference/policy-library) page for configuration and usage. +# Account Setup +Source: https://docs.chain.link/ace/getting-started/account-setup +Last Updated: 2026-04-15 -## Reference token implementations + -The repository includes reference token contracts that demonstrate full ACE integration: +This page walks through the shared setup steps for all ACE users. Whether you use the [Policy Manager](/ace/getting-started/policy-manager) to enforce compliance on smart contracts or the [Identity Manager](/ace/getting-started/identity-manager) to manage cross-chain identities and credentials, complete these steps first. -- [ERC-20 Compliance Token](https://github.com/smartcontractkit/chainlink-ace/tree/main/packages/tokens/erc-20) - — A policy-protected ERC-20 with frozen token handling. -- [ERC-3643 Compliance Token](https://github.com/smartcontractkit/chainlink-ace/tree/main/packages/tokens/erc-3643) - — A compliant implementation of the ERC-3643 T-REX standard. +## 1. Create your organization -## Repository documentation +Go to [app.chain.link](https://app.chain.link) and create an account or sign in. -The policy-management docs folder contains detailed guides: +Once signed in, click **"My Org"** in the bottom-left corner of the sidebar to open the Organization page. Your **Organization ID** is displayed in the page header — copy it. -- [Concepts](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/CONCEPTS.md) - — Architecture, policy flow, extractors, mappers, and context handling -- [API Guide](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/API_GUIDE.md) - — Task-oriented guide with code examples for common operations -- [API Reference](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/API_REFERENCE.md) - — Complete interface specifications with function signatures and events -- [Custom Policies Tutorial](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/CUSTOM_POLICIES_TUTORIAL.md) - — End-to-end walkthrough for building a custom policy contract -- [Policy Ordering Guide](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/POLICY_ORDERING_GUIDE.md) - — How evaluation order affects transaction outcomes -- [Security Considerations](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/SECURITY.md) - — Trust model, gas considerations, and context handling +## 2. Share your Organization ID -## Related pages +Share your Organization ID with your Chainlink contact so they can enable the ACE service for your organization. You cannot create API keys or use ACE until this step is complete. -- [Architecture](/ace/concepts/architecture) — How PolicyEngine contracts fit into the ACE system -- [Policy Management](/ace/concepts/policy-management) — Conceptual overview of policy chains and evaluation -- [Policy Library](/ace/reference/policy-library) — Pre-built policy implementations with configuration details + ---- +## 3. Create an API key -# Policy Library -Source: https://docs.chain.link/ace/reference/policy-library -Last Updated: 2026-04-20 + -ACE ships with a library of pre-built, audited policies that cover the most common compliance and access control patterns. Each policy is a standalone smart contract that plugs into a PolicyEngine and evaluates transactions at runtime. +Once your account has been provisioned for the ACE service, create an API key for authentication: -For guidance on combining policies and understanding execution order, see [Policy Ordering & Composition](/ace/concepts/policy-ordering). +1. Log in to the [Chainlink App](https://app.chain.link), click **"My Org"** at the bottom of the left sidebar, then select the **"APIs"** tab. +2. Click **"+ Organization API"**. +3. Enter a name for the key and select an expiration period (1 day, 1 month, or 1 year). +4. Click **"Generate"**. The API key is displayed once — copy it immediately. -## Policy summary + -| Policy | Description | -| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| [AllowPolicy](/ace/reference/policy-library/allow-policy) | Maintains an allowlist. Rejects the transaction if any checked address is **not** on the list. | -| [BypassPolicy](/ace/reference/policy-library/bypass-policy) | Maintains an allowlist. If **all** checked addresses are on the list, immediately allows the transaction and **skips all remaining policies**. | -| [RejectPolicy](/ace/reference/policy-library/reject-policy) | Maintains a denylist. Rejects the transaction if any checked address **is** on the list. | -| [OnlyAuthorizedSenderPolicy](/ace/reference/policy-library/only-authorized-sender-policy) | Rejects the transaction if the sender (`msg.sender`) is not on the authorized list. | -| [RoleBasedAccessControlPolicy](/ace/reference/policy-library/role-based-access-control-policy) | Maps roles to function selectors. Rejects if the sender does not hold a role allowed for the called function. | -| [MaxPolicy](/ace/reference/policy-library/max-policy) | Rejects the transaction if the extracted value exceeds a configured maximum. | -| [VolumePolicy](/ace/reference/policy-library/volume-policy) | Rejects the transaction if the extracted value is below a minimum or above a maximum. | -| [VolumeRatePolicy](/ace/reference/policy-library/volume-rate-policy) | Tracks cumulative volume per account per time period. Rejects if the period's cap would be exceeded. | -| [SecureMintPolicy](/ace/reference/policy-library/secure-mint-policy) | Checks a Chainlink Proof of Reserve feed. Rejects if minting would push total supply beyond verified reserves. | -| [IntervalPolicy](/ace/reference/policy-library/interval-policy) | Divides time into repeating slot-based cycles. Rejects if the current slot is outside the allowed window. | -| [PausePolicy](/ace/reference/policy-library/pause-policy) | Global toggle. Rejects every transaction when paused; passes through when unpaused. | -| [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) | Checks each address against configured credential requirements. Rejects if any address lacks required credentials. | -| [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) | Routes each address to a credential group, then validates it against that group's requirements. Rejects if no group matches or requirements fail. | -| [CertifiedActionDONValidatorPolicy](/ace/reference/policy-library/certified-action-don-validator-policy) | Validates DON-issued permits delivered on-chain via the Keystone Forwarder. Rejects if no valid permit exists. | +You will use this key in the `Authorization` header for all [Coordinator API](/api/ace/coordinator/docs) and [Reporting API](/api/ace/reporting/docs) calls: ---- +```bash +curl https://ace.api.chain.link/v1/ \ + -H "Authorization: Apikey " +``` -# AllowPolicy -Source: https://docs.chain.link/ace/reference/policy-library/allow-policy -Last Updated: 2026-04-20 +## 4. Set up CRE Connect Wallets -The AllowPolicy restricts transactions to a known set of approved addresses. It checks every address extracted from the transaction against an allowlist and immediately rejects if any of them is not on the list, halting all subsequent policy checks. +Before you can use ACE — whether from the Platform UI or the Coordinator API — you need a **CRE Connect Wallet** on each blockchain network where you want to operate. -## Configuration +A CRE Connect Wallet is a dedicated onchain smart contract wallet that acts as the execution gateway for all ACE actions on a given chain. When you trigger an action (deploy a policy engine, register an identity, issue a credential), the ACE platform executes the blockchain transaction through your CRE Connect Wallet. For a full explanation of how the signing and ownership model works, see [Signing & Ownership Model](/ace/concepts/signing-ownership). -### Address allowlist +Key points: -The allowlist defines which addresses are permitted to participate in transactions protected by this policy. The list starts empty at deployment and must be populated afterward — until you add at least one address, every transaction will be rejected. +- **One wallet per chain.** You need a CRE Connect Wallet on every network where you plan to use ACE. See [Supported Networks](/ace/supported-networks) for available chains. +- **You own it.** The `owner_address` you provide when creating the wallet becomes the owner of the CRE Connect Wallet onchain. +- **Chainlink operates through it.** Chainlink is registered as an authorized operator — allowed to execute operations on your behalf, but unable to change ownership or authorization settings. +- **Self-signing organizations** must also provide the `address` of the ECDSA signer authorized to sign operations. See [Signing & Ownership Model](/ace/concepts/signing-ownership) for details on how each signing model works. -Each address is added or removed individually. When a protected function is called, the extractor provides one or more addresses from the transaction (for example, both the sender and receiver of a token transfer). Which addresses the policy receives depends on the [mapper configuration](/ace/concepts/policy-management#worked-example-erc-20-transfer). All of those addresses must be on the allowlist for the transaction to pass. +## What happens next -## Runtime behavior +Your next step depends on what you need to do: -The policy expects a variable number of parameters from the extractor, each an address. All provided addresses are checked against the allowlist. +| If you need to... | Next step | +| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| Enforce compliance policies on smart contracts | Deploy a PolicyEngine and integrate your contract. Continue to the [Policy Manager Quick Start](/ace/getting-started/policy-manager). | +| Manage cross-chain identities and issue credentials | Set up identity and credential registries. Continue to the [Identity Manager Quick Start](/ace/getting-started/identity-manager). | -- **`run()`** — Reverts if *any* address is not on the allowlist. Returns `Continue` otherwise. -- **`postRun()`** — No state changes. +Many organizations do both. Start with whichever is most relevant to your first use case — the quick start guides cross-reference each other where the workflows intersect. -## API reference +--- -### Setter functions +# Policy Manager Quick Start +Source: https://docs.chain.link/ace/getting-started/policy-manager +Last Updated: 2026-05-26 -- **`allowAddress(address account)`** — Adds an address to the allowlist. Reverts if the address is already listed. -- **`disallowAddress(address account)`** — Removes an address from the allowlist. Reverts if the address is not listed. + -### View functions +This guide walks you through the **Policy Manager** — the ACE component for attaching and configuring compliance policies on smart contracts. By the end you will have a policy-protected contract running on a supported network. -- **`addressAllowed(address account)`** — Returns `true` if the address is on the allowlist. +### 1. Prerequisites -## Use cases +- Solidity basics +- **Must-read before proceeding:** [Signing & Ownership Model](/ace/concepts/signing-ownership) — understand how ACE manages keys, who owns what, and how the delegated trust model works -- **Regulated access** — Restrict token transfers to a known set of approved addresses. -- **Gradual rollout** — Start with a small allowlist and expand as new addresses are vetted. +### 2. Account setup -## Source +Complete the [Account Setup](/ace/getting-started/account-setup) steps — organization creation, API key generation, and CRE Connect Wallet deployment — before proceeding. -[AllowPolicy.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/policies/AllowPolicy.sol) +### 3. Create a PolicyEngine ---- +A **PolicyEngine** is the onchain contract that evaluates compliance rules on your smart contract. When you create a PolicyEngine, you also attach **extractors** — modules that decode transaction calldata so the PolicyEngine can evaluate policies against function arguments (sender, recipient, amount, etc.). -# BypassPolicy -Source: https://docs.chain.link/ace/reference/policy-library/bypass-policy -Last Updated: 2026-04-20 +#### Verify deployment -The BypassPolicy gives privileged addresses a fast path through the policy chain. If *all* addresses extracted from the transaction are on the bypass list, the policy immediately allows the transaction and skips every remaining policy in the chain. If any address is not on the list, the policy returns `Continue` and lets subsequent policies decide. +The PolicyEngine and its extractors start with `"creation_pending"` / `"inactive"` status while the onchain transactions are processed. Poll until everything is ready: -This is the only built-in policy that returns `Allowed`. +```bash +curl https://ace.api.chain.link/v1/policy-engines/ \ + -H "Authorization: Apikey " +``` - +Check two things in the response: -## Configuration +1. **PolicyEngine deployed** — Every entry in `onchain_policy_engines` shows `"status": "created"`. +2. **Extractors active** — Every entry in `extractor_registrations[].onchain_extractor_registrations` shows `"status": "active"`. -### Address allowlist +In the Platform UI, confirm the engine status is **Active** and extractors appear in the engine settings page. -The bypass list defines which addresses can skip the rest of the policy chain. The list starts empty at deployment and must be populated afterward. + -Each address is added or removed individually. When a protected function is called, the extractor provides one or more addresses from the transaction. Which addresses the policy receives depends on the [mapper configuration](/ace/concepts/policy-management#worked-example-erc-20-transfer). All of those addresses must be on the bypass list for the fast path to activate — if even one address is missing, the policy returns `Continue` and normal policy evaluation continues. +#### Save the PolicyEngine addresses -## Runtime behavior +Copy the `address` value from each entry in `onchain_policy_engines` — or from the engine settings page in the UI — you need these addresses to deploy or upgrade your smart contract in the next step. Each chain has a different PolicyEngine contract address. -The policy expects a variable number of parameters from the extractor, each an address. +### 4. Integrate your contract -- **`run()`** — Returns `Allowed` if all provided addresses are on the bypass list, skipping all subsequent policies. Returns `Continue` otherwise. -- **`postRun()`** — No state changes. +See [Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible) for a full overview of what your contract needs. In short: -## API reference +- Inherit from `PolicyProtected` (or `PolicyProtectedUpgradeable` for upgradeable contracts) +- Add the `runPolicy` modifier to the functions you want to protect +- Pass the PolicyEngine address during deployment or initialization -### Setter functions +**Choose your path:** -- **`allowAddress(address account)`** — Adds an address to the bypass list. Reverts if the address is already listed. -- **`disallowAddress(address account)`** — Removes an address from the bypass list. Reverts if the address is not listed. +#### New contract -### View functions +If you are building a new token or contract from scratch, ACE provides reference implementations you can use as a starting point: -- **`addressAllowed(address account)`** — Returns `true` if the address is on the bypass list. +- **ERC-20** — see [Building an ERC-20 Compliance Token](/ace/guides/policy-manager/contracts/erc20-token) for the full guide +- **ERC-3643** — see [Building an ERC-3643 Compliance Token](/ace/guides/policy-manager/contracts/erc3643-token) for the full guide -## Use cases +#### Existing contract -- **Privileged access** — Let administrators or system contracts bypass compliance checks entirely. -- **Layered permissions** — Place at the top of a policy chain so that listed addresses skip volume limits, identity checks, and other restrictions. +If you have an already-deployed contract you want to add ACE compliance to, this involves modifying your implementation contract, testing, and executing a proxy upgrade. This is typically the longest step in the onboarding process. -## Source +- See [Upgrading Existing Contracts](/ace/guides/policy-manager/contracts/upgrade-existing) for the step-by-step guide +- Non-upgradeable contracts require alternative approaches — contact your Chainlink representative for guidance -[BypassPolicy.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/policies/BypassPolicy.sol) +### 5. Register your contract as a target ---- +After deploying your contract, register it as a target under your PolicyEngine. Provide the contract name, type, protected methods, and on-chain addresses for each chain where the contract is deployed. -# CertifiedActionDONValidatorPolicy -Source: https://docs.chain.link/ace/reference/policy-library/certified-action-don-validator-policy -Last Updated: 2026-08-18 +```bash +curl -X POST https://ace.api.chain.link/v1/targets \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "title": "My ERC-20 Token", + "description": "Production ERC-20 token with compliance enforcement", + "policy_engine_id": "", + "protected_methods": [ + "transfer(address,uint256)", + "transferFrom(address,address,uint256)", + #[any other methods you want to protect] + ], + "desired_default_allow": true, + "metadata": {"contract_type": "ERC-20"}, + "onchain_targets": [ + { + "chain_selector": "16015286601757825753", + "address": "0xYourContractAddressOnSepolia" + }, + { + "chain_selector": "3478487238524512106", + "address": "0xYourContractAddressOnArbitrumSepolia" + }, + #[any other chains where your contract is deployed] + ] + }' +``` -The CertifiedActionDONValidatorPolicy (CADV) is the onchain contract that validates permits generated by [offchain policy execution](/ace/concepts/off-chain-policies). When a Chainlink DON workflow approves an action, it delivers a permit through the Keystone Forwarder. The CADV stores the permit and verifies it when the protected function is called. +Include an entry in `onchain_targets` for every chain where you deployed the contract. Once registered, your target appears in the ACE Platform dashboard under your policy engine. -For a full explanation of how offchain policies work, what you can connect to, and how the permit flow operates, see [Off-Chain Policy Execution](/ace/concepts/off-chain-policies). +For a full description of all fields and options, see [Managing Targets](/ace/guides/policy-manager/manage-targets#register-a-target). -## Permit lifecycle +### 6. Post-setup checklist -Managed offchain permits are **pre-presented**: the DON writes the permit to the CADV before the user submits the protected transaction. The user does not include permit bytes in the transaction. +Before creating policies, confirm that every component is in the expected state. You can verify each of these with a single API call. -The CADV indexes a permit by its transaction intent, which includes: +| Check | What to look for | +| ---------------------------- | -------------------------------------------------------------------------------------------------------- | +| CRE Connect Wallets created | `GET /wallets` — every wallet shows `"status": "created"` | +| PolicyEngine deployed | `GET /policy-engines/` — every `onchain_policy_engines[].status` is `"created"` | +| Extractors active | Same response — every `extractor_registrations[].onchain_extractor_registrations[].status` is `"active"` | +| Contract visible in platform | Your target contract appears in the ACE Platform dashboard | -- Caller address -- Protected target address -- Function selector -- Ordered parameters produced by the target function's extractor +### 7. Create and configure policies -At execution time, the policy engine passes the actual caller, target, selector, and extracted parameters to the CADV. The call is allowed only when they match a stored, valid permit. After the protected call succeeds, the policy engine invokes `postRun`, which increments the permit's usage count and emits `PermitUsed`. +From the UI or API, create policy instances and attach them to your contract's protected functions. See [Managing Policies](/ace/guides/policy-manager/manage-policies) for creating and configuring policies, then [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) for attaching them to specific functions on your contracts. -The managed `wallet_risk_scoring` policy currently creates permits with: +### 8. Test it -- `maxUses = 1` — the permit can authorize one successful transaction. -- `expiry = 0` — the permit does not expire. +Make a transaction against your protected contract, verify the policy enforces correctly, and check the results in the [Reporting Manager](/ace/concepts/reporting). -These values are fixed in the current Beta release. +--- -The CADV also emits `PermitStored` when the workflow publishes a permit. ACE waits for this event before changing the corresponding evaluation status to `ready`. +# Identity Manager Quick Start +Source: https://docs.chain.link/ace/getting-started/identity-manager +Last Updated: 2026-05-26 -## Combining with other policies + -The CADV is attached to a target function like any other policy. It can run before or after onchain policies such as identity, allowlist, or volume checks. All policies in the function's policy chain must allow the call. +This guide walks you through the **Identity Manager** — the ACE component for managing identity registries, registering cross-chain identities (CCIDs), and issuing credentials such as Proof of Identity, accreditation proofs, or sanctions clearance. By the end you will have identities registered and credentials issued on a supported network. -See [Policy Ordering & Composition](/ace/concepts/policy-ordering) for guidance on evaluation order. +### 1. Prerequisites -## Related pages +- Familiarity with [Cross-Chain Identity](/ace/concepts/cross-chain-identity) concepts — CCIDs, credential registries, credential types, and credential sources +- **Must-read before proceeding:** [Signing & Ownership Model](/ace/concepts/signing-ownership) — understand how ACE manages keys, who owns what, and how the delegated trust model works -- [Off-Chain Policy Execution](/ace/concepts/off-chain-policies) — conceptual overview of managed and custom offchain policies -- [Evaluation API](/ace/reference/api/evaluation) — the runtime API for requesting managed offchain permits -- [Policy Ordering & Composition](/ace/concepts/policy-ordering) — guidance on evaluation order +### 2. Account setup -## Source +Complete the [Account Setup](/ace/getting-started/account-setup) steps — organization creation, API key generation, and CRE Connect Wallet deployment — before proceeding. -- [CertifiedActionDONValidatorPolicy.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/policies/CertifiedActionDONValidatorPolicy.sol) +### 3. Set up your registries ---- +A **registry** is the top-level resource that groups an **identity registry** and a **credential registry**, deployed together on each chain you operate on. -# CredentialRegistryIdentityValidatorPolicy -Source: https://docs.chain.link/ace/reference/policy-library/credential-registry-identity-validator-policy -Last Updated: 2026-08-18 +- The identity registry maps wallet addresses to cross-chain identities (CCIDs) +- The credential registry stores credential attestations linked to those CCIDs -The CredentialRegistryIdentityValidatorPolicy validates that accounts involved in a transaction hold the required credentials from ACE's [Cross-Chain Identity](/ace/concepts/cross-chain-identity) infrastructure. It checks each account against configured credential sources (IdentityRegistry + CredentialRegistry pairs) and credential requirements (which credential types must be present and how many validations are needed). +See [Managing Registries](/ace/guides/identity-manager/manage-registries) for full details. -This is the primary policy for enforcing identity-based compliance such as KYC, accreditation, or sanctions screening. +### 4. Define credential types - +Credential types represent the categories of attestation you issue — for example, Proof of Identity, accredited investor, or sanctions clearance. Each credential type is scoped to a specific registry and identified by a `credential_type` string that gets hashed on-chain to a `credential_type_hash`. This value is hashed using keccak256 — the standard cryptographic hash function used by Ethereum and EVM-compatible blockchains — and the resulting `credential_type_hash` is what gets recorded on-chain and referenced by policies. -## Configuration +### 5. Register identities and issue credentials -Both properties below can be set when the policy is first deployed and updated afterward by the policy owner. Credential sources contain on-chain addresses and must be configured per network. Credential requirements define rules that apply across chains. +A cross-chain identity (CCID) aggregates one or more wallet addresses across EVM chains into a single logical entity. When you register an identity, you provide the on-chain addresses that belong to that entity and ACE writes the mapping into the identity registry on each relevant chain. -### Credential sources +A single CCID can span multiple chains and addresses — for example, one entity might have wallets on Ethereum, Arbitrum, and Avalanche that all resolve to the same CCID. -A credential source defines *where* to look up identity and credential data for a given credential type. Each source is a tuple of: +Credentials are attestations linked to a registered identity. During Beta, ACE uses an **attestation-only** model — the Identity Manager asserts that a credential holds for a given CCID, and the credential registry records that attestation on-chain. -- **Credential type ID** — A `bytes32` identifier for the credential type this source applies to (e.g., KYC, accreditation). -- **Identity registry address** — The IdentityRegistry contract that maps wallet addresses to Cross-Chain Identifiers (CCIDs). -- **Credential registry address** — The CredentialRegistry contract that stores credentials linked to CCIDs. -- **Data validator address** (optional) — A contract that performs additional validation on the credential data. Set to `address(0)` for attestation-only checks, or a Data Validator address to validate credential contents (for example, a jurisdiction allow/deny list). See [Credential data and privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy) for a full explanation of attestation-only vs. Credential Data Validator checks. +### 6. Verify via Reporting -Multiple sources can be registered for the same credential type. The policy checks all configured sources and counts validations across them. Source uniqueness is determined by the `(identityRegistry, credentialRegistry)` pair — two sources with the same registry pair but different `dataValidator` addresses are considered duplicates. +After issuing credentials, confirm they are visible and queryable through the **Reporting Manager**. The Reporting Manager provides a read-only view of all identities and credentials across your registries, which Policy Managers rely on when evaluating identity-based policies at transaction time. -**Limits:** Up to 8 sources per credential type. +- Open the Reporting API to query credentials by identity, entity, or registry +- See [Reporting](/ace/concepts/reporting) for details on how reporting data flows into policy evaluation -### Credential requirements +Once credentials appear in reporting, Policy Managers can reference them in identity-based policies such as the [Credential Registry Identity Validator](/ace/reference/policy-library/credential-registry-identity-validator-policy). -A credential requirement defines *what* credentials an account must hold. Each requirement specifies: +### 7. What's next -- **Requirement ID** — A unique `bytes32` identifier for this requirement. -- **Credential type IDs** — An array of `bytes32` credential types to check (e.g., KYC, accreditation). -- **Minimum validations** — How many of the listed credential types must validate successfully. Must be at least 1. -- **Invert flag** — When `true`, the check passes if the credential does *not* exist. This is useful for "must not be sanctioned" checks, where you want the transaction to succeed only if the account does not hold a sanctions credential. +Explore the detailed guides for each Identity Manager workflow: -An account passes a requirement if it accumulates at least `minValidations` successful validations across the listed credential types and configured sources. +- [Managing Identities](/ace/guides/identity-manager/manage-identities) — add, update, and remove CCIDs and their on-chain address mappings +- [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types) — create and organize the credential categories your registry supports +- [Managing Credentials](/ace/guides/identity-manager/manage-credentials) — issue, revoke, and set expiration on credentials +- [Managing Registries](/ace/guides/identity-manager/manage-registries) — view and manage your identity and credential registry deployments -**Limits:** Up to 8 requirements total, up to 32 credential types per requirement. +--- -## Runtime behavior +# Policy Manager Guides +Source: https://docs.chain.link/ace/guides/policy-manager +Last Updated: 2026-07-17 -The policy expects a variable number of parameters from the extractor, each an address to validate. Every address is checked against all configured requirements. +These guides cover the day-to-day operations of a Policy Manager — from integrating your smart contracts with ACE to configuring and managing compliance policies. -For each address, the validation process: + -1. Iterates through all credential requirements. -2. For each requirement, checks the listed credential types against each configured source. -3. For each source, looks up the account's CCID in the IdentityRegistry, then checks whether the CredentialRegistry holds the credential for that CCID. -4. If a DataValidator is configured, it additionally validates the credential data. -5. Counts successful validations. If the count meets `minValidations`, the requirement passes. +## How it all fits together -- **`run()`** — Reverts if any address fails any requirement. Returns `Continue` if all addresses pass all requirements. -- **`postRun()`** — No state changes. +The Policy Manager revolves around a handful of entities that work together to enforce compliance on your smart contracts. Understanding how they relate to each other makes the individual guides much easier to follow. -## API reference +- **PolicyEngine** — The on-chain orchestrator that evaluates policies. Everything — targets, policy instances, and extractors — is scoped to a single engine. +- **Extractors** — Modules that decode transaction calldata into named parameters (sender, amount, etc.) so policies can evaluate them. Attached to the engine at creation time. +- **Target** — A smart contract registered under an engine via the API or UI. +- **Policy Implementation** — A reusable compliance rule from the [Policy Library](/ace/reference/policy-library) (e.g., allowlist, volume limit, pause toggle). +- **Policy Instance** — A deployed copy of an implementation, configured with your specific parameters and scoped to an engine. +- **Protection** — The binding between a policy instance and a specific function on a target contract. This is what makes a function "policy-protected." +- **Data Validator** — An optional contract attached to an identity policy's credential source that validates the *contents* of a credential (e.g., a jurisdiction allow/deny list), not just its existence. +- **Managed offchain policy (MVP)** — A CRE workflow and onchain validator managed by Chainlink that evaluate external risk data before issuing a permit for a specific transaction intent. -### Setter functions +### Typical setup flow -**Credential sources:** +1. [Create a PolicyEngine](/ace/guides/policy-manager/manage-engines) with extractors for your contract type (ERC-20 or ERC-3643). +2. [Integrate your contract](/ace/guides/policy-manager/contracts/ace-compatible) by inheriting `PolicyProtected` and adding the `runPolicy` modifier. +3. Deploy or upgrade your contract — it [appears as a target](/ace/guides/policy-manager/manage-targets) automatically. +4. [Create policy instances](/ace/guides/policy-manager/manage-policies) from the Policy Library with your configuration. +5. [Attach policies to functions](/ace/guides/policy-manager/manage-protections) by creating protections. -- **`addCredentialSource(CredentialSourceInput input)`** — Adds a source for a credential type. Reverts if the source already exists or if the maximum number of sources (8) for that credential type has been reached. -- **`removeCredentialSource(bytes32 credentialTypeId, address identityRegistry, address credentialRegistry)`** — Removes a source. Reverts if the source is not found. +## Smart contract integration -**Credential requirements:** +- [Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible) — what your contract needs to work with ACE (inheriting `PolicyProtected`, adding the `runPolicy` modifier) +- [Building a New ERC-20 Token](/ace/guides/policy-manager/contracts/erc20-token) — reference implementation for a compliance-ready ERC-20 token +- [Building a New ERC-3643 Token](/ace/guides/policy-manager/contracts/erc3643-token) — reference implementation for an ERC-3643 security token +- [Upgrading Existing Contracts](/ace/guides/policy-manager/contracts/upgrade-existing) — how to add ACE compliance to an already-deployed upgradeable contract +- [Security Considerations](/ace/guides/policy-manager/contracts/security-considerations) — key security patterns and pitfalls when integrating with ACE -- **`addCredentialRequirement(CredentialRequirementInput input)`** — Adds a requirement. Reverts if a requirement with the same ID already exists or if the configuration is invalid. -- **`removeCredentialRequirement(bytes32 requirementId)`** — Removes a requirement. Reverts if the requirement ID is not found. +## Policy engine and policy management + +- [Managing Policy Engines](/ace/guides/policy-manager/manage-engines) — create, view, update, and archive policy engines +- [Managing Targets](/ace/guides/policy-manager/manage-targets) — register your deployed contracts as targets under a policy engine +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — browse policy implementations, create and configure policy instances +- [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) — bind policy instances to specific functions on your target contracts +- [Managing Data Validators](/ace/guides/policy-manager/manage-data-validators) — enforce rules on credential contents (e.g., jurisdiction allow/deny lists) by attaching Data Validators to identity policies +- [Custom Policies](/ace/guides/policy-manager/custom-policies) — write, deploy, and register your own policy contract, then use it like a library policy + + + +- [Offchain Policies](/ace/guides/policy-manager/offchain-policies) — understand the managed and custom models, configure the managed wallet screening MVP, and integrate offchain permits + +--- + +# Making Your Contract ACE-Compatible +Source: https://docs.chain.link/ace/guides/policy-manager/contracts/ace-compatible +Last Updated: 2026-05-26 + +A contract is ACE-compatible when it can route function calls through a PolicyEngine for compliance checks before execution. This requires four things: inheriting a base contract, marking functions for protection, connecting to a PolicyEngine, and registering extractors. This page explains what each requirement means at a high level — the sub-pages linked below walk through the implementation in detail. + +For background on how these components interact, see the [Architecture page](/ace/concepts/architecture#policy-management-contracts) and the [Policy Management](/ace/concepts/policy-management) concepts page. + + + +## What your contract needs + +### 1. Inherit from PolicyProtected + +Your contract must inherit from `PolicyProtected` (for new contracts) or `PolicyProtectedUpgradeable` (for contracts deployed behind a proxy that need an upgrade path). This base contract provides: + +- The `runPolicy` and `runPolicyWithContext` modifiers that hook your functions into the policy system. +- Functions to attach and manage the connection to a PolicyEngine. +- Context handling for passing additional data (like offchain signatures) to policies. + +### 2. Add the runPolicy modifier to protected functions + +Any function that should be subject to compliance checks needs the `runPolicy` modifier. The modifier intercepts the call and routes it through the [PolicyEngine](/ace/guides/policy-manager/manage-engines) before your function body executes. + +```solidity +// Before: no compliance checks +function transfer(address to, uint256 amount) public returns (bool) { + return super.transfer(to, amount); +} + +// After: the PolicyEngine checks all attached policies before execution +function transfer(address to, uint256 amount) public runPolicy returns (bool) { + return super.transfer(to, amount); +} +``` + +You choose which functions to protect. Unprotected functions continue to work normally without any policy checks. + +### 3. Connect to a PolicyEngine + +Your contract must be connected to a [PolicyEngine](/ace/guides/policy-manager/manage-engines) — the central orchestrator that holds all policies and executes them in order when a protected function is called. The connection is established during initialization (for [new contracts](/ace/guides/policy-manager/contracts/new-contract)) or migration (for [upgrades](/ace/guides/policy-manager/contracts/upgrade-existing)). + +### 4. Register extractors for protected functions + +[Extractors](/ace/guides/policy-manager/manage-policies#managing-extractors) are helper contracts that parse the calldata of your protected functions into named parameters (for example, `to` and `value` for an ERC-20 `transfer`). Policies use these named parameters to make their decisions — a volume limit policy reads `value`, a sanctions check reads `to`. + +One extractor is registered per function signature. To bind policies to specific functions, see [Protecting Target Functions](/ace/guides/policy-manager/manage-protections). + + + +## Integration paths + +How you integrate ACE depends on where your contract is today: + +- **[Building a New Contract](/ace/guides/policy-manager/contracts/new-contract)** — Starting a new project? ACE provides audited reference implementations for ERC-20 and ERC-3643 tokens that come pre-integrated with PolicyProtected. This is the fastest path. + +- **[Upgrading Existing Contracts](/ace/guides/policy-manager/contracts/upgrade-existing)** — Already have a deployed contract behind a proxy? You can add ACE compliance through a standard proxy upgrade without disrupting existing state, balances, or integrations. + +- **Non-upgradeable contract?** — If your contract is not behind a proxy, the upgrade guide also covers [alternative approaches](/ace/guides/policy-manager/contracts/upgrade-existing#alternatives-for-non-upgradeable-contracts) — wrapped contracts, contract migration, and edge protection — each with different tradeoffs depending on your constraints. + +--- + +# Building a New Contract +Source: https://docs.chain.link/ace/guides/policy-manager/contracts/new-contract +Last Updated: 2026-03-31 + +If you are starting a new project, ACE provides audited reference implementations that come pre-integrated with PolicyProtected. You do not need to implement the ACE integration yourself — these contracts are ready to deploy and protect with policies. + +If you already have a deployed contract, see [Upgrading Existing Contracts](/ace/guides/policy-manager/contracts/upgrade-existing) instead. + +## Reference implementations + +ACE offers two token implementations, each designed for different regulatory contexts: + +- **ComplianceTokenERC20** — A policy-protected ERC-20 token with advanced frozen token handling, force transfers, and mint/burn controls. +- **ComplianceTokenERC3643** — A compliant implementation of the [ERC-3643 (T-REX)](https://eips.ethereum.org/EIPS/eip-3643) standard, using ACE Cross-Chain Identity instead of ONCHAINID and ACE Policy Management instead of T-REX ModularCompliance. + +Both implementations inherit from `PolicyProtectedUpgradeable` and must be deployed behind a proxy. During ACE Beta, deployment is managed through the ACE Platform. + +## Choosing between ERC-20 and ERC-3643 + +The right choice depends on your regulatory requirements, the asset type you are tokenizing, and how you need frozen tokens to behave. + +| Aspect | ERC-20 Compliance Token | ERC-3643 Compliance Token | +| ------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| **Standard** | ERC-20 | ERC-3643 (T-REX) | +| **Regulatory context** | Broad — suitable for any token that needs policy-based compliance | Securities — designed for regulated securities and financial instruments | +| **Identity system** | ACE Cross-Chain Identity | ACE Cross-Chain Identity (replaces ONCHAINID) | +| **Compliance system** | ACE Policy Management | ACE Policy Management (replaces T-REX ModularCompliance) | +| **Frozen token behavior** | Strict preservation — frozen tokens remain frozen during burns and force transfers | Operational flexibility — burns and force transfers can automatically unfreeze tokens when needed | +| **Pause support** | No built-in pause (use a PausePolicy instead) | Built-in `pause`/`unpause` with `whenNotPaused` modifier | +| **Batch operations** | No | Yes — batch transfer, mint, burn, freeze/unfreeze | +| **CCIP admin** | `getCCIPAdmin()` returns the contract owner | Not included | + +### Frozen token behavior explained + +The most significant difference between the two implementations is how frozen tokens are handled during administrative operations: + +**ERC-20 approach (strict preservation):** When an admin performs a burn or force transfer on an account with frozen tokens, the frozen balance is preserved. The operation only succeeds if the account has sufficient *unfrozen* balance. This means an admin must explicitly unfreeze tokens before they can be burned or force-transferred. + +**ERC-3643 approach (automatic unfreezing):** When an admin performs a burn or force transfer, the contract automatically unfreezes tokens if the unfrozen balance is insufficient. This follows the T-REX philosophy that administrative actions should not be blocked by frozen status — the admin has already decided the operation is necessary. + +### When to choose each + +**Choose ERC-20** when: + +- You need a general-purpose compliant token without a specific regulatory framework requirement. +- You want strict control over frozen tokens — every unfreeze must be an explicit administrative action. +- You plan to integrate with CCIP for cross-chain transfers. + +**Choose ERC-3643** when: + +- You are tokenizing regulated securities and need compliance with the ERC-3643 standard. +- Your regulatory framework requires or benefits from the T-REX interface (existing tooling, auditor familiarity). +- You need batch operations for managing large numbers of holders efficiently. +- You prefer operational flexibility for administrative actions on frozen tokens. + +## Next steps + +- **[Building an ERC-20 Compliance Token](/ace/guides/policy-manager/contracts/erc20-token)** — Detailed guide for deploying and configuring the ERC-20 reference implementation. +- **[Building an ERC-3643 Compliance Token](/ace/guides/policy-manager/contracts/erc3643-token)** — Detailed guide for deploying and configuring the ERC-3643 reference implementation. + + + +--- + +# Building an ERC-20 Compliance Token +Source: https://docs.chain.link/ace/guides/policy-manager/contracts/erc20-token +Last Updated: 2026-03-31 + +The `ComplianceTokenERC20` is a ready-to-deploy, policy-protected ERC-20 token provided as an ACE reference implementation. It inherits `PolicyProtectedUpgradeable`, routes every state-changing function through a PolicyEngine, and is designed for deployment behind a proxy. + +For a comparison with the ERC-3643 variant and guidance on which to choose, see [Building a New Contract](/ace/guides/policy-manager/contracts/new-contract#choosing-between-erc-20-and-erc-3643). + +## What makes it ACE-compatible + +The token satisfies all the requirements described in [Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible): + +1. **Inherits `PolicyProtectedUpgradeable`** — The contract calls `__PolicyProtected_init` during initialization, which sets the contract owner and connects it to a PolicyEngine. +2. **All state-changing functions are policy-protected** — Every function that modifies balances, allowances, or frozen state carries the `runPolicy` or `runPolicyWithContext` modifier. The PolicyEngine evaluates all attached policies before the function body executes. +3. **ERC-7201 namespaced storage** — All token state lives in a dedicated `ComplianceTokenStoreERC20` storage struct, following the [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) pattern for safe upgradeable storage. + +## Protected functions + +Every state-changing function on the token is policy-protected. The [`runPolicy` modifier](/ace/concepts/policy-management#the-policy-execution-flow) intercepts each call and routes it through the PolicyEngine, which evaluates all attached policies before the function body executes. Functions that need to pass additional context (such as offchain signatures or metadata) use [`runPolicyWithContext`](/ace/concepts/policy-management#the-context-parameter) instead, which forwards a `bytes context` parameter to every policy in the chain. + +### ERC-20 standard + +| Function | Modifier | Description | +| -------------------------------- | ----------- | ---------------------------------------------------------------- | +| `transfer(to, amount)` | `runPolicy` | Transfer tokens from the caller to another address. | +| `transferFrom(from, to, amount)` | `runPolicy` | Transfer tokens on behalf of another address using an allowance. | +| `approve(spender, amount)` | `runPolicy` | Set an allowance for a spender. | + +### Minting and burning + +| Function | Modifier | Description | +| ------------------------ | ----------- | ------------------------------------------------ | +| `mint(to, amount)` | `runPolicy` | Create new tokens and assign them to an address. | +| `burn(amount)` | `runPolicy` | Destroy tokens from the caller's balance. | +| `burnFrom(from, amount)` | `runPolicy` | Destroy tokens from another address. | + +### Administrative and compliance + +| Function | Modifier | Description | +| ------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------------ | +| `freeze(account, amount, context)` | `runPolicyWithContext` | Freeze a specific amount of tokens on an account. Frozen tokens cannot be transferred or burned. | +| `unfreeze(account, amount, context)` | `runPolicyWithContext` | Unfreeze a previously frozen amount on an account. | +| `forceTransfer(from, to, amount, context)` | `runPolicyWithContext` | Administratively move tokens between accounts, subject to frozen balance checks. | + + + +## Frozen token behavior + +`ComplianceTokenERC20` uses a **strict preservation** model for frozen tokens: + +- **Available balance** = total balance - frozen balance. Every transfer, burn, and force transfer checks that the sender has sufficient *unfrozen* balance and reverts if not. +- **No automatic unfreezing** — Frozen tokens remain frozen during all operations. An administrator must explicitly call `unfreeze` before those tokens can be moved or burned. +- **Pre-freezing** — Tokens can be frozen on an account before they are received. The frozen amount is tracked independently from the balance, so an admin can set a frozen amount in advance and the restriction takes effect as soon as tokens arrive. + +This model provides maximum compliance control: every change to frozen status is an explicit, auditable administrative action. + + + +## Storage layout + +All token state is stored in `ComplianceTokenStoreERC20`, which uses ERC-7201 namespaced storage at a deterministic slot: + +| Field | Type | Description | +| ---------------- | ------------------------------------------------- | --------------------------------- | +| `name` | `string` | Token name. | +| `symbol` | `string` | Token symbol. | +| `decimals` | `uint8` | Decimal precision for display. | +| `totalSupply` | `uint256` | Total supply of tokens. | +| `balances` | `mapping(address => uint256)` | Per-account token balances. | +| `allowances` | `mapping(address => mapping(address => uint256))` | Per-account spender allowances. | +| `frozenBalances` | `mapping(address => uint256)` | Per-account frozen token amounts. | +| `data` | `mapping(bytes32 => bytes)` | Generic storage for extensions. | + +## CCIP compatibility + +The contract exposes `getCCIPAdmin()`, which returns the contract owner. This enables integration with [Chainlink CCIP](/ccip) for cross-chain token transfers by identifying the admin authorized to configure the token's CCIP settings. + +## Reference implementation + +The full source code for the ERC-20 compliance token: + +- [ComplianceTokenERC20.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/tokens/erc-20/src/ComplianceTokenERC20.sol) — Token contract with all protected functions and frozen token logic. +- [ComplianceTokenStoreERC20.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/tokens/erc-20/src/ComplianceTokenStoreERC20.sol) — ERC-7201 namespaced storage layout. + +--- + +# Building an ERC-3643 Compliance Token +Source: https://docs.chain.link/ace/guides/policy-manager/contracts/erc3643-token +Last Updated: 2026-03-31 + +The `ComplianceTokenERC3643` implements the [ERC-3643 (T-REX)](https://eips.ethereum.org/EIPS/eip-3643) `IToken` interface but replaces the canonical T-REX identity and compliance systems with ACE equivalents. It inherits `PolicyProtectedUpgradeable`, is deployed behind a proxy, and routes all state-changing functions through a PolicyEngine. + +For a comparison with the ERC-20 variant and guidance on which to choose, see [Building a New Contract](/ace/guides/policy-manager/contracts/new-contract#choosing-between-erc-20-and-erc-3643). + +## What makes it ACE-compatible + +The token satisfies all the requirements described in [Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible): + +1. **Inherits `PolicyProtectedUpgradeable`** — The contract calls `__PolicyProtected_init` during initialization, which sets the contract owner and connects it to a PolicyEngine. +2. **All state-changing functions are policy-protected** — Every function that modifies state carries the `runPolicy` modifier. The PolicyEngine evaluates all attached policies before the function body executes. +3. **ERC-7201 namespaced storage** — All token state lives in a dedicated `ComplianceTokenStoreERC3643` storage struct, following the [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) pattern for safe upgradeable storage. + +## How it differs from canonical T-REX + +This implementation keeps the `IToken` interface that T-REX tooling and auditors expect, but swaps out the two internal subsystems for ACE equivalents: + +### Identity: ACE Cross-Chain Identity replaces ONCHAINID + +The canonical T-REX stack uses ONCHAINID for on-chain identity claims. This implementation replaces it with ACE's [Cross-Chain Identity](/ace/concepts/cross-chain-identity) infrastructure (IdentityRegistry and CredentialRegistry). The legacy interface stubs remain to satisfy `IToken` but are not functional: + +- `identityRegistry()` returns `address(0)`. +- `onchainID()` returns `address(0)`. +- `setIdentityRegistry()` reverts with "Not implemented". +- `setOnchainID()` reverts with "Not implemented". + +Identity verification is handled through ACE policies that validate credentials against the IdentityRegistry and CredentialRegistry. + +### Compliance: ACE Policy Management replaces ModularCompliance + +The canonical T-REX stack uses `ModularCompliance` for transfer rules. This implementation replaces it with ACE's [Policy Management](/ace/concepts/policy-management) system, where compliance rules are defined as policies attached to the PolicyEngine. The legacy stub remains: + +- `compliance()` returns `address(0)`. +- `setCompliance()` reverts with "Not implemented". + +### Wallet recovery not implemented + +- `recoveryAddress()` reverts with "Not implemented". Wallet recovery is not supported in this implementation. + +## Protected functions + +Every state-changing function on the token is policy-protected with [`runPolicy`](/ace/concepts/policy-management#the-policy-execution-flow), which intercepts each call and routes it through the PolicyEngine. The engine evaluates all attached policies before the function body executes. Functions that interact with user balances also carry the `whenNotPaused` modifier, which checks the token's pause state before proceeding. + +### Transfers + +| Function | Modifiers | Description | +| ---------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `transfer(to, amount)` | `whenNotPaused`, `runPolicy` | Transfer tokens from the caller to another address. Checks that neither wallet is frozen and that the sender has sufficient unfrozen balance. | +| `transferFrom(from, to, amount)` | `whenNotPaused`, `runPolicy` | Transfer tokens on behalf of another address using an allowance. Same frozen and balance checks as `transfer`. | +| `forcedTransfer(from, to, amount)` | `runPolicy` | Administrative transfer that auto-unfreezes tokens if the unfrozen balance is insufficient. | + +### Allowances + +| Function | Modifiers | Description | +| --------------------------------------------- | ---------------------------- | ------------------------------- | +| `approve(spender, amount)` | `whenNotPaused`, `runPolicy` | Set an allowance for a spender. | +| `increaseAllowance(spender, addedValue)` | `whenNotPaused`, `runPolicy` | Increase an existing allowance. | +| `decreaseAllowance(spender, subtractedValue)` | `whenNotPaused`, `runPolicy` | Decrease an existing allowance. | + +### Minting and burning + +| Function | Modifiers | Description | +| --------------------------- | ----------- | ---------------------------------------------------------------------------------------------- | +| `mint(to, amount)` | `runPolicy` | Create new tokens and assign them to an address. | +| `burn(userAddress, amount)` | `runPolicy` | Destroy tokens from an address. Auto-unfreezes tokens if the unfrozen balance is insufficient. | + +### Freezing + +| Function | Modifiers | Description | +| -------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------- | +| `setAddressFrozen(userAddress, freeze)` | `runPolicy` | Freeze or unfreeze an entire address. A frozen address cannot send or receive tokens through regular transfers. | +| `freezePartialTokens(userAddress, amount)` | `runPolicy` | Freeze a specific amount of tokens on an account. | +| `unfreezePartialTokens(userAddress, amount)` | `runPolicy` | Unfreeze a previously frozen amount on an account. | + +### Token administration + +| Function | Modifiers | Description | +| ------------------- | ----------- | ---------------------------------------------------------------- | +| `pause()` | `runPolicy` | Pause the token. All functions with `whenNotPaused` will revert. | +| `unpause()` | `runPolicy` | Unpause the token. | +| `setName(name)` | `runPolicy` | Update the token name. | +| `setSymbol(symbol)` | `runPolicy` | Update the token symbol. | + +## Frozen token behavior + +`ComplianceTokenERC3643` uses an **automatic unfreezing** model, following the standard T-REX approach. There are two independent freeze mechanisms: + +- **Address freeze** — A boolean flag (`frozen[address]`) that blocks an address from sending or receiving tokens through regular `transfer` and `transferFrom` calls. +- **Partial token freeze** — A numeric amount (`frozenTokens[address]`) that restricts how many of an account's tokens can be moved. Available balance = total balance - frozen tokens. + +Regular transfers check both: the wallet must not be address-frozen, and the transfer amount must not exceed the unfrozen balance. + +**Administrative operations auto-unfreeze.** When `forcedTransfer` or `burn` is called and the unfrozen balance is insufficient, the contract automatically reduces `frozenTokens` by the shortfall and emits a `TokensUnfrozen` event. This means administrative actions are never blocked by partial frozen status — the admin has already decided the operation is necessary. + + + +## Built-in pause + +The token includes a built-in `pause`/`unpause` mechanism. Both functions are policy-protected. When paused, all functions carrying the `whenNotPaused` modifier revert — this includes `transfer`, `transferFrom`, `approve`, `increaseAllowance`, and `decreaseAllowance`. + +Administrative functions (`mint`, `burn`, `forcedTransfer`, freeze operations) do **not** carry `whenNotPaused` and remain callable while the token is paused. + + + +## Batch operations + +The ERC-3643 token supports batch operations for managing large numbers of holders efficiently: + +- `batchTransfer` — Transfer to multiple recipients in a single transaction. +- `batchForcedTransfer` — Force-transfer between multiple address pairs. +- `batchMint` — Mint to multiple recipients. +- `batchBurn` — Burn from multiple addresses. +- `batchSetAddressFrozen` — Freeze or unfreeze multiple addresses. +- `batchFreezePartialTokens` — Freeze token amounts on multiple accounts. +- `batchUnfreezePartialTokens` — Unfreeze token amounts on multiple accounts. + +Each batch function delegates to its single-item counterpart in a loop, so every individual operation goes through `runPolicy` independently. + +## Storage layout + +All token state is stored in `ComplianceTokenStoreERC3643`, which uses ERC-7201 namespaced storage at a deterministic slot: + +| Field | Type | Description | +| --------------- | ------------------------------------------------- | --------------------------------- | +| `tokenName` | `string` | Token name. | +| `tokenSymbol` | `string` | Token symbol. | +| `tokenDecimals` | `uint8` | Decimal precision for display. | +| `tokenPaused` | `bool` | Whether the token is paused. | +| `totalSupply` | `uint256` | Total supply of tokens. | +| `balances` | `mapping(address => uint256)` | Per-account token balances. | +| `allowances` | `mapping(address => mapping(address => uint256))` | Per-account spender allowances. | +| `frozen` | `mapping(address => bool)` | Per-account address freeze flag. | +| `frozenTokens` | `mapping(address => uint256)` | Per-account frozen token amounts. | + +## Reference implementation + +The full source code for the ERC-3643 compliance token: + +- [ComplianceTokenERC3643.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/tokens/erc-3643/src/ComplianceTokenERC3643.sol) — Token contract implementing the `IToken` interface with ACE policy protection. +- [ComplianceTokenStoreERC3643.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/tokens/erc-3643/src/ComplianceTokenStoreERC3643.sol) — ERC-7201 namespaced storage layout. + +--- + +# Upgrading Existing Contracts +Source: https://docs.chain.link/ace/guides/policy-manager/contracts/upgrade-existing +Last Updated: 2026-05-26 + +This guide explains how to add ACE compliance to a contract that is already deployed. The process is a standard proxy upgrade — your existing state (balances, allowances, mappings) is fully preserved, your contract address stays the same, and all existing integrations continue to work. + + + + + +## Prerequisites + +Before starting, you should be familiar with: + +- [ACE Architecture](/ace/concepts/architecture) — how PolicyEngine, policies, and extractors work together +- [Policy Management](/ace/concepts/policy-management) — the execution model and policy outcomes + +### Your contract must be upgradeable + +This guide covers contracts deployed behind a proxy pattern — UUPS, Transparent Proxy, or Beacon Proxy. You need upgrade authority over the contract. + +If your contract is **not upgradeable**, see [Alternatives for non-upgradeable contracts](#alternatives-for-non-upgradeable-contracts) below. + +## Key concept: Storage safety with ERC-7201 + +When upgrading a contract, new variables must not overwrite existing state. `PolicyProtectedUpgradeable` uses [ERC-7201 namespaced storage](https://eips.ethereum.org/EIPS/eip-7201), which isolates all ACE data in a deterministic storage slot that cannot collide with your existing storage layout. + +```solidity +bytes32 private constant STORAGE_LOCATION = + keccak256(abi.encode(uint256(keccak256("chainlink.ace.PolicyProtected")) - 1)) + & ~bytes32(uint256(0xff)); +``` + +This formula produces a storage location that is guaranteed not to overlap with Solidity's default sequential storage layout. Your existing balances, allowances, and other state remain untouched. + +## Choosing your approach + +There are two ways to integrate ACE into an upgradeable contract: + +| Aspect | Approach 1: Extend PolicyProtectedUpgradeable | Approach 2: Implement IPolicyProtected | +| ------------------------- | --------------------------------------------- | ------------------------------------------- | +| **Bytecode impact** | +5-6 KB | +1-2 KB | +| **Implementation effort** | Add inheritance + modifiers | Write storage, context, and execution logic | +| **Maintenance** | Inherits ACE updates automatically | You maintain all custom code | +| **Risk** | Lower — proven patterns | Higher — custom code means custom bugs | + +**Recommendation:** Use Approach 1 unless your contract is near the 24 KB bytecode limit or you need custom control over how context is stored or policies are executed. + +## Approach 1: Extend PolicyProtectedUpgradeable (recommended) + +This approach inherits from `PolicyProtectedUpgradeable`, which provides built-in modifiers and automatic storage management. + +### Step 1: Update contract inheritance + +Add `PolicyProtectedUpgradeable` to your inheritance chain. + +**Before:** + +```solidity +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract MyToken is Initializable, ERC20Upgradeable, OwnableUpgradeable { + // ... +} +``` + +**After:** + +```solidity +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {PolicyProtectedUpgradeable} from "@chainlink/policy-management/core/PolicyProtectedUpgradeable.sol"; + +contract MyToken is PolicyProtectedUpgradeable, ERC20Upgradeable, UUPSUpgradeable { + // ... +} +``` + + + +### Step 2: Add a migration function + +Your original `initialize()` has already been called, so you cannot modify it. Instead, add a migration function using `reinitializer`: + +```solidity +function migrateToACE(address policyEngine) public reinitializer(2) onlyOwner { + __PolicyProtected_init_unchained(policyEngine); +} +``` + +`reinitializer(2)` ensures this migration runs exactly once (version 1 was your original `initialize()`). If you have had previous upgrades with reinitializers, increment the version accordingly. + +`__PolicyProtected_init_unchained()` stores the PolicyEngine address in namespaced storage and registers your contract with the PolicyEngine. + + + +If you need to switch to a different PolicyEngine later, call `attachPolicyEngine(newAddress)` (owner-only). + +### Step 3: Add runPolicy to protected functions + +Add the `runPolicy` modifier to each function that should be subject to policy checks. + +**Before:** + +```solidity +function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); +} + +function transfer(address to, uint256 amount) public virtual override returns (bool) { + return super.transfer(to, amount); +} +``` + +**After:** + +```solidity +function mint(address to, uint256 amount) public runPolicy { + _mint(to, amount); +} + +function transfer(address to, uint256 amount) public virtual override runPolicy returns (bool) { + return super.transfer(to, amount); +} +``` + +Access control (restricting who can mint, for example) is now enforced through policies rather than traditional `onlyOwner` modifiers. This lets you change access rules by updating policies without upgrading the contract. + +For functions that need additional data passed to policies (signatures, proofs), use `runPolicyWithContext`: + +```solidity +function forceTransfer( + address from, + address to, + uint256 amount, + bytes calldata context +) public runPolicyWithContext(context) { + _update(from, to, amount); +} +``` + +### Complete before/after example + +**Before (standard upgradeable ERC-20):** + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +contract MyToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable { + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner) public initializer { + __ERC20_init("MyToken", "MTK"); + __Ownable_init(initialOwner); + } + + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} +} +``` + +**After (with ACE integration):** + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {PolicyProtectedUpgradeable} from "@chainlink/policy-management/core/PolicyProtectedUpgradeable.sol"; + +contract MyToken is PolicyProtectedUpgradeable, ERC20Upgradeable, UUPSUpgradeable { + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner) public initializer { + __ERC20_init("MyToken", "MTK"); + __Ownable_init(initialOwner); + } + + function migrateToACE(address policyEngine) public reinitializer(2) onlyOwner { + __PolicyProtected_init_unchained(policyEngine); + } + + function mint(address to, uint256 amount) public runPolicy { + _mint(to, amount); + } + + function transfer(address to, uint256 amount) + public + virtual + override + runPolicy + returns (bool) + { + return super.transfer(to, amount); + } + + function transferFrom(address from, address to, uint256 amount) + public + virtual + override + runPolicy + returns (bool) + { + return super.transferFrom(from, to, amount); + } + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} +} +``` + +**Key changes:** + +1. Import and inherit `PolicyProtectedUpgradeable` (remove explicit `Initializable` and `OwnableUpgradeable` — they are inherited through `PolicyProtectedUpgradeable`). +2. Add `migrateToACE()` with `reinitializer(2)`. +3. Add `runPolicy` to functions that need policy protection. + +## Approach 2: Implement IPolicyProtected (advanced) + +If your contract is near the 24 KB bytecode limit or you need custom control over policy execution, you can implement the `IPolicyProtected` interface directly instead of inheriting from `PolicyProtectedUpgradeable`. This adds only \~1-2 KB of bytecode but requires more code. + +### What you must implement + +You are responsible for: + +1. **Storage** — Storing the PolicyEngine address and per-sender context using ERC-7201 namespaced storage. +2. **Policy execution** — Calling `policyEngine.run()` with the correct payload in each protected function. +3. **Context handling** — Storing, retrieving, and clearing context data. +4. **Registration** — Attaching to and detaching from the PolicyEngine. +5. **ERC-165 support** — Implementing `supportsInterface()`. + +### Interface methods + +```solidity +interface IPolicyProtected { + function attachPolicyEngine(address policyEngine) external; + function getPolicyEngine() external view returns (address); + function setContext(bytes calldata context) external; + function getContext() external view returns (bytes memory); + function clearContext() external; +} +``` + +| Method | Purpose | +| -------------------- | ----------------------------------------------- | +| `attachPolicyEngine` | Registers your contract with a PolicyEngine | +| `getPolicyEngine` | Returns the current PolicyEngine address | +| `setContext` | Stores context data for the next protected call | +| `getContext` | Retrieves stored context for the current caller | +| `clearContext` | Clears context after use to prevent replay | + +### Implementation skeleton + +The following skeleton shows the key pieces for an ERC-20 token. It uses the same migration pattern as Approach 1, but all ACE logic is implemented manually. + +```solidity +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {IPolicyProtected} from "@chainlink/policy-management/interfaces/IPolicyProtected.sol"; +import {IPolicyEngine} from "@chainlink/policy-management/interfaces/IPolicyEngine.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +contract MyToken is ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable, IPolicyProtected { + + // --- ERC-7201 Namespaced Storage --- + + struct ACEStorage { + address policyEngine; + mapping(address => bytes) senderContext; + } + + // Replace with your calculated ERC-7201 storage slot + bytes32 private constant ACE_STORAGE_LOCATION = 0x...; + + function _getACEStorage() private pure returns (ACEStorage storage $) { + assembly { + $.slot := ACE_STORAGE_LOCATION + } + } + + // --- Migration --- + + function migrateToACE(address policyEngine) public reinitializer(2) onlyOwner { + _attachPolicyEngine(policyEngine); + } + + // --- IPolicyProtected --- + + function attachPolicyEngine(address policyEngine) external onlyOwner { + _attachPolicyEngine(policyEngine); + } + + function _attachPolicyEngine(address policyEngine) internal { + require(policyEngine != address(0), "Zero address"); + ACEStorage storage $ = _getACEStorage(); + $.policyEngine = policyEngine; + IPolicyEngine(policyEngine).attach(); + } + + function getPolicyEngine() public view returns (address) { + return _getACEStorage().policyEngine; + } + + function setContext(bytes calldata context) external { + _getACEStorage().senderContext[msg.sender] = context; + } + + function getContext() public view returns (bytes memory) { + return _getACEStorage().senderContext[msg.sender]; + } + + function clearContext() public { + delete _getACEStorage().senderContext[msg.sender]; + } + + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return interfaceId == type(IPolicyProtected).interfaceId || + interfaceId == type(IERC165).interfaceId; + } + + // --- Policy Execution --- + + function _runPolicy() internal { + ACEStorage storage $ = _getACEStorage(); + require($.policyEngine != address(0), "PolicyEngine not set"); + + bytes memory context = getContext(); + IPolicyEngine($.policyEngine).run( + IPolicyEngine.Payload({ + selector: msg.sig, + sender: msg.sender, + data: msg.data[4:], + context: context + }) + ); + + if (context.length > 0) { + clearContext(); + } + } + + // --- Protected Functions --- + + function transfer(address to, uint256 amount) public virtual override returns (bool) { + _runPolicy(); + return super.transfer(to, amount); + } + + // ... other protected functions follow the same pattern +} +``` + + + +## Execute the upgrade + +At this point your updated implementation contract is ready. You need the PolicyEngine address to proceed. + +### Pre-upgrade checklist + +**Development:** + +- Updated contract compiles successfully +- Final bytecode is under 24 KB +- Unit tests pass +- Integration tests with PolicyEngine pass + +**Infrastructure:** + +- PolicyEngine address received from the ACE Platform (Beta) or deployed by your team (GA) + +### Upgrade execution + +Deploy the new implementation, then execute the upgrade and migration in one transaction. The exact pattern depends on your proxy type: + +**UUPS:** + +```solidity +bytes memory data = abi.encodeCall(MyToken.migrateToACE, (policyEngineAddress)); +MyToken(proxyAddress).upgradeToAndCall(newImplementationAddress, data); +``` + +**Transparent Proxy:** + +```solidity +bytes memory data = abi.encodeCall(MyToken.migrateToACE, (policyEngineAddress)); +ProxyAdmin(proxyAdminAddress).upgradeAndCall(proxyAddress, newImplementationAddress, data); +``` + +**Beacon Proxy:** + +```solidity +// Beacon does not support upgradeAndCall — execute separately +UpgradeableBeacon(beaconAddress).upgradeTo(newImplementationAddress); +MyToken(proxyAddress).migrateToACE(policyEngineAddress); +``` + +### Post-upgrade verification + +- `getPolicyEngine()` returns the correct address +- Protected functions trigger policy checks +- Policies allow and reject transactions as expected +- Existing balances, allowances, and other state are unchanged + +## Alternatives for non-upgradeable contracts + +If your contract is not deployed behind a proxy, a standard upgrade is not possible. Depending on your situation, there are three alternative approaches to bring ACE compliance to your application. + +### Wrapped contract + +Deploy a new ACE-compatible wrapper contract that sits in front of your original contract. Users interact with the wrapper, which enforces policies before delegating calls to the underlying contract. + +**How it works:** The wrapper inherits from `PolicyProtected` and exposes the same external interface as the original contract. Each function on the wrapper calls `runPolicy`, then forwards the call to the original contract. The original contract remains completely untouched. + +**When to use:** Your contract's logic does not need to change, but you need compliance checks on interactions with it. Works well for contracts where you can redirect user traffic to a new entry point. + +**Tradeoffs:** + +- The wrapper has a **different contract address**, so integrators (DEXs, lending protocols, front ends) must update their references. +- If wrapping a token, users may need to **migrate balances** or **re-approve allowances** to the wrapper. +- Adds a layer of indirection, which slightly increases gas costs per call. + +### Contract migration + +Deploy a brand-new ACE-native contract and migrate state from the old contract to the new one. The new contract is built from scratch with `PolicyProtected` integrated from the start. + +**How it works:** You take a snapshot of the old contract's state (balances, allowances, roles, etc.) and seed the new contract with that data during deployment or through a claim-based migration. The old contract is then deprecated or paused. + +**When to use:** You want no wrapper indirection, no legacy contract to maintain. Particularly suited for tokens where a coordinated migration event is feasible (for example, a token swap or airdrop). + +**Tradeoffs:** + +- Requires a **coordinated migration event** — all holders and integrators must move to the new contract. +- The new contract has a **different address**, which affects all downstream integrations. +- Migration patterns (snapshot + airdrop, or claim-based redemption) add operational complexity. +- The old contract must be handled (paused, drained, or deprecated) to prevent confusion. + +### Edge protection + +Instead of modifying your contract, apply ACE policies at the integration points that interact with it — for example, a DEX pool, a bridge, or a lending protocol front end. + +**How it works:** The protected contract is not your original contract, but the integration layer. A DEX pool contract or a custom router contract inherits `PolicyProtected` and enforces compliance checks before interacting with your original token or vault. Your contract is never modified. + +**When to use:** Modifying the contract is not an option (immutable deployment, no migration path), and you can control the integration points where compliance matters. Works well when compliance is needed at specific boundaries rather than on every direct interaction. + +**Tradeoffs:** + +- **Does not protect direct contract interactions** — any user who calls your contract directly (bypassing the protected integration point) is not subject to policy checks. +- Only covers the specific integration points where ACE is applied. Comprehensive coverage requires wrapping all relevant entry points. +- The original contract's functionality is unchanged, which may be a regulatory concern if direct access remains open. + + + +## FAQ + +### Will this upgrade overwrite my existing state? + +No. `PolicyProtectedUpgradeable` uses ERC-7201 namespaced storage, which stores ACE data in an isolated slot. Your existing balances, allowances, and all other state remain untouched. + +### What happens to token balances and allowances? + +All state is preserved. The upgrade replaces the implementation contract (the code), but all state lives in the proxy's storage and is not affected. Users do not need to re-approve. + +### What about tokens held in external contracts (DEXs, protocols)? + +Unaffected. Your contract address does not change, so all existing integrations continue working. The only difference is that transactions may revert if policies reject them. + +### Can I protect only some functions? + +Yes. You only add `runPolicy` to the functions you want to protect. All other functions continue working normally without policy checks. + +### Can I update policies after the upgrade? + +Yes. Policies can be added, removed, reordered, and reconfigured through the ACE Platform without touching your contract code. + +### What if I need to switch to a different PolicyEngine? + +Call `attachPolicyEngine(newAddress)` (owner-only). This detaches the old engine and registers your contract with the new one. Once ACE is integrated, a PolicyEngine is always required — you cannot set it to the zero address. + +### How many policies can I attach to a single function? + +The PolicyEngine supports up to 8 policies per function selector. + +### My contract is near the 24 KB bytecode limit. What can I do? + +Use [Approach 2](#approach-2-implement-ipolicyprotected-advanced), which adds only \~1-2 KB. You can also enable the Solidity optimizer with higher runs, move logic to external libraries, or split functionality into separate contracts. + +--- + +# Security Considerations +Source: https://docs.chain.link/ace/guides/policy-manager/contracts/security-considerations +Last Updated: 2026-03-31 + +This page covers the implementation-level security concerns that developers should understand when integrating with ACE contracts — trust boundaries, external call risks, and defensive programming patterns. + +For governance-level security (administration controls, execution ordering, registry governance, privacy guarantees), see the [Security Model](/ace/concepts/security) concepts page. + +## Trust model for policies and extractors + +The PolicyEngine delegates trust to the individual Policy, Extractor, and Mapper contracts it is configured to use. A vulnerability in any one of these components can compromise the entire system. + +### Policy trust + +A malicious or poorly written policy can introduce vulnerabilities at two levels: + +- **The `run()` function** (read-only) — A policy that always returns Allow would bypass all subsequent policies. A policy that makes dangerous external calls could be exploited for denial of service. +- **The `postRun()` function** (state-changing) — This function executes after a successful check and can modify onchain state. A malicious postRun could drain funds, change ownership, or corrupt state. + +Only install trusted, audited policies. ACE provides a library of pre-built, audited policies for common use cases. + +### Extractor trust + +The PolicyEngine relies on Extractors to correctly and honestly parse transaction calldata. If an Extractor is compromised, it could misrepresent the data that policies use for their decisions. For example, an Extractor could report a false `value` for a transfer, causing a VolumePolicy to undercount and allow transactions that should be blocked. + +### External call risks + +Many policies make external calls during execution — for example, querying a credential registry or checking an external data source. Since most policy `run()` functions are `view` (read-only), traditional reentrancy attacks are not possible. However, other risks apply: + +- **Denial of service** — A malicious external contract could revert or consume excessive gas, causing the entire policy chain to fail. +- **Inconsistent reads** — External contract state could change between multiple calls within the same transaction. +- **Gas exhaustion** — Deep call chains across multiple policies with external calls could exceed gas limits. + +For policies with state-changing functions (like `postRun()`), traditional reentrancy protections should be considered if those functions make external calls. + +**Mitigation:** Only interact with well-established, audited external contracts in policy logic. Implement proper error handling so that policies gracefully handle external contract failures rather than cascading reverts. + +## Context handling and race conditions + +The [context parameter](/ace/concepts/policy-management#the-context-parameter) is a powerful feature for passing arbitrary data to policies, but it requires careful handling. + +When using the two-step method (calling `setContext` followed by the protected function), the context is stored per sender in the PolicyProtected contract. If context is set but not consumed in the same atomic transaction, stale context from a previous call could be reused. In contracts used by multiple senders (like relayers or governance contracts), one user's context could potentially be overwritten by another before it is consumed. + +**Mitigation:** Always set and consume context within the same atomic transaction. For contracts with multiple concurrent users, prefer the direct argument method (`runPolicyWithContext`) over the two-step approach. + +## Non-reverting view functions + +All validator functions in the Cross-Chain Identity system — `validate()`, `validateCredentialData()`, and related view functions — must **never revert** under any circumstances. They must always return a boolean result. + +This is a critical reliability requirement. If a validator reverted during a policy check (for example, because an external call to a credential registry failed), it would break the entire policy chain for that transaction. The PolicyEngine would not be able to distinguish between "credential is invalid" and "validator is broken." + +Implementations must use defensive programming patterns: + +- Wrap external calls in try-catch blocks. +- Return `false` on any external call failure rather than allowing the revert to propagate. +- Validate all inputs before making external calls. + +This guarantees that the policy chain always completes and returns a definitive result, even when downstream dependencies fail. + +--- + +# Managing Policy Engines +Source: https://docs.chain.link/ace/guides/policy-manager/manage-engines +Last Updated: 2026-05-26 + +A **policy engine** is the on-chain orchestrator that evaluates policies whenever a protected function is called. Each policy engine is deployed as a smart contract on one or more chains, and all your targets, policies, and protections are scoped to a specific engine. For a deeper explanation of how policy engines fit into the architecture, see [Architecture](/ace/concepts/architecture) and [Policy Management](/ace/concepts/policy-management). + + + +## Create a policy engine + +## View policy engines + +## Update a policy engine + +You can update a policy engine's name, description, and extractor associations. + +## Add or remove extractors + +Extractors decode transaction calldata into named parameters (sender, recipient, amount, etc.) so policies can evaluate them. If you forgot to attach an extractor during engine creation or need to remove one, use the `PUT /policy-engines/{id}` endpoint. + + + +### Add a missing extractor + +First, retrieve your engine to see which extractors are currently attached: + +```bash +curl https://ace.api.chain.link/v1/policy-engines/ \ + -H "Authorization: Apikey " +``` + +Check the `extractor_registrations` array in the response. Then send a PUT request that includes the existing extractor IDs plus the new one: + +```bash +curl -X PUT https://ace.api.chain.link/v1/policy-engines/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "My Policy Engine", + "extractor_ids": [ + "d4b8cd51-7d5a-487a-9ba5-bb7236e3184c", + "572201bb-170b-4fda-ab89-a65b5bbc594b", + "f17bbe8b-8462-4dd7-8fb7-a4973dff04fc" + ], + "onchain_policy_engines": [ + { "chain_selector": "16015286601757825753" }, + { "chain_selector": "3478487238524512106" }, + { "chain_selector": "14767482510784806043" }, + { "chain_selector": "16281711391670634445" }, + { "chain_selector": "10344971235874465080" } + ] + }' +``` + +In this example, `f17bbe8b-8462-4dd7-8fb7-a4973dff04fc` is the new extractor being added alongside two that were already attached. + +### Remove an extractor + +Send a PUT request with the `extractor_ids` array that **omits** the extractor you want to detach: + +```bash +curl -X PUT https://ace.api.chain.link/v1/policy-engines/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "My Policy Engine", + "extractor_ids": [ + "d4b8cd51-7d5a-487a-9ba5-bb7236e3184c", + "572201bb-170b-4fda-ab89-a65b5bbc594b" + ], + "onchain_policy_engines": [ + { "chain_selector": "16015286601757825753" }, + { "chain_selector": "3478487238524512106" }, + { "chain_selector": "14767482510784806043" }, + { "chain_selector": "16281711391670634445" }, + { "chain_selector": "10344971235874465080" } + ] + }' +``` + + + +## Archive a policy engine + +Archiving a policy engine deactivates it and prevents any further operations. All policy instances associated with the engine must be archived first. + + + +## Related pages + +- [Architecture](/ace/concepts/architecture) — how PolicyEngine contracts fit into the ACE system +- [Policy Management](/ace/concepts/policy-management) — how policy chains and evaluation work +- [Managing Targets](/ace/guides/policy-manager/manage-targets) — register contracts to protect under an engine +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — create and configure policy instances +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema + +--- + +# Managing Targets +Source: https://docs.chain.link/ace/guides/policy-manager/manage-targets +Last Updated: 2026-04-06 + +A **target** is a smart contract protected by ACE. After deploying your [ACE-compatible contract](/ace/guides/policy-manager/contracts/ace-compatible), you register it as a target under a PolicyEngine. Once registered, you can configure the [default policy result](#default-allow-behavior) and attach [policies](/ace/guides/policy-manager/manage-policies) to its functions via [protections](/ace/guides/policy-manager/manage-protections). + + + +## Register a target + +After deploying your contract, register it as a target with a `POST` request. Provide the contract name, type, protected methods, and on-chain addresses: + +```bash +curl -X POST https://ace.api.chain.link/v1/targets \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "title": "My ERC-20 Token", + "description": "Production ERC-20 token with compliance enforcement", + "policy_engine_id": "", + "protected_methods": [ + "transfer(address,uint256)", + "transferFrom(address,address,uint256)" + #[any other methods you want to protect] + ], + "desired_default_allow": true, + "metadata": {"contract_type": "ERC-20"}, + "onchain_targets": [ + { + "chain_selector": "16015286601757825753", + "address": "0xYourContractAddressOnSepolia" + }, + { + "chain_selector": "3478487238524512106", + "address": "0xYourContractAddressOnArbitrumSepolia" + }, + #[any other chains where your contract is deployed] + ] + }' +``` + +| Field | Required | Description | +| ----------------------- | -------- | ------------------------------------------------------------- | +| `title` | Yes | Human-readable name for the target | +| `description` | No | Description of the contract | +| `policy_engine_id` | Yes | UUID of the policy engine to associate with | +| `protected_methods` | No | Array of function signatures that can be protected | +| `desired_default_allow` | No | Whether to allow transactions by default (default: `true`) | +| `onchain_targets` | No | Array of objects with `chain_selector` and contract `address` | +| `metadata` | No | Arbitrary JSON metadata (e.g., `{"contract_type": "ERC-20"}`) | + +## Default allow behavior + +The **default policy result** controls what happens when a transaction passes through the entire policy chain and no policy explicitly returns Allow or Reject (i.e., every policy returns Continue). This is configured per target contract via the `desired_default_allow` field: + +- **`true` (default)** — The transaction is allowed. This is appropriate when you want policies to act as blockers (reject specific cases), and everything else passes through. +- **`false`** — The transaction is rejected. This is appropriate for allowlist-style enforcement where only explicitly approved transactions proceed. + +For more on how policy evaluation ordering works, see [Policy Ordering & Composition](/ace/concepts/policy-ordering#the-default-result). + +### Change the default policy result + +## View targets + +## Update a target + +You can update a target's name, description, contract type, protected methods, default allow behavior, and on-chain addresses. + + + +## Link targets + +When you deploy your ACE-compatible contract on a new chain, the control plane detects it automatically and creates a separate **detected target** (titled "unknown target"). Rather than managing each chain deployment as its own target, you can **merge** detected targets into an existing target to keep a single multi-chain target with all its on-chain addresses in one place. + +### Conditions + +A detected target can be merged (linked) into an existing target when: + +- The detected target was auto-discovered — it still has the default "unknown target" title. +- The detected target has at least one on-chain address. +- A valid destination target exists in the same policy engine: it must be a different target, already named, and deployed on a **different chain** than the source (no shared chain selectors). + +If no valid destination exists, the detected target cannot be merged — you can only rename it via **Edit details**. + +### How it works + +Merging transfers the on-chain addresses from the source target(s) to the destination target, then archives the sources. After the merge, the destination target contains all chain deployments and any protections remain on the destination. + + + +## Archive a target + +Archiving a target removes it from active use. All target protections associated with the target must be archived first. + + + +## Related pages + +- [Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible) — how to integrate `PolicyProtected` into your contract +- [Managing Policy Engines](/ace/guides/policy-manager/manage-engines) — create the engine your target will use +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — create policy instances to attach to your target +- [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) — attach policies to specific functions on your target +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema + +--- + +# Managing Policies +Source: https://docs.chain.link/ace/guides/policy-manager/manage-policies +Last Updated: 2026-04-15 + +This guide covers how to browse available policy types, create policy instances, and configure their parameters. For attaching policies to specific functions on your contracts, see [Protecting Target Functions](/ace/guides/policy-manager/manage-protections). + +## Policy implementations vs policy instances + +ACE distinguishes between two concepts: + +- **Policy implementation** — A reusable policy template (smart contract code) that defines specific compliance logic, such as an allowlist check or volume limit. ACE provides a [pre-built library](/ace/reference/policy-library) of audited implementations, and you can register your own [custom policy](/ace/guides/policy-manager/custom-policies) implementations, which are scoped to your organization. +- **Policy instance** — A deployed copy of a policy implementation, configured with your specific parameters and associated with a [policy engine](/ace/guides/policy-manager/manage-engines). You create a policy instance from an implementation and then attach it to target functions via [protections](/ace/guides/policy-manager/manage-protections). + +For example, the "Allow List Policy" implementation can be instantiated multiple times with different allowlists. + +## Browse policy implementations + +## Create a policy instance + +A policy instance is created from a policy implementation and deployed on-chain within a policy engine. + +## View and filter policies + +## Update policy configuration + +After deploying a policy instance, you can update its on-chain configuration parameters — for example, adding an address to an allowlist or changing a volume threshold — without redeploying the policy. + +## Archive a policy + +Archiving a policy instance removes it from active use. All target protections that reference this policy must be archived first. + + + +## Related pages + +- [Policy Management](/ace/concepts/policy-management) — how policy chains and evaluation work +- [Policy Library](/ace/reference/policy-library) — pre-built policy implementations with configuration details +- [Managing Policy Engines](/ace/guides/policy-manager/manage-engines) — create the engine your policies belong to +- [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) — attach policy instances to target functions +- [Policy Ordering & Composition](/ace/concepts/policy-ordering) — how to compose effective rulesets +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema + +--- + +# Protecting Target Functions +Source: https://docs.chain.link/ace/guides/policy-manager/manage-protections +Last Updated: 2026-05-26 + +A **target protection** is the link between a [policy instance](/ace/guides/policy-manager/manage-policies) and a specific function on a [target contract](/ace/guides/policy-manager/manage-targets). When a user calls the protected function, the policy engine evaluates the bound policies in order and decides whether to allow or reject the transaction. Target protections are the final step in setting up on-chain compliance enforcement. + +## Prerequisites + +Before creating a target protection, you need: + +1. A [policy engine](/ace/guides/policy-manager/manage-engines) deployed on your target chains. +2. A [target contract](/ace/guides/policy-manager/manage-targets) registered under that engine. +3. A [policy instance](/ace/guides/policy-manager/manage-policies) created from a policy implementation and associated with the same engine. +4. Extractors attached to the engine that support the function signatures you want to protect (see the [Policy Manager Quick Start](/ace/getting-started/policy-manager#extractor-ids-for-api-creation) for the full list). + +## Create a target protection + +A protection binds a policy instance to a specific function on your target contract. Once created, every call to that function is evaluated against the policy. + +## Position and evaluation order + +The `desired_position` determines the order in which policies are evaluated when a protected function is called: + +- **Position 0** is evaluated first. +- Policies are evaluated sequentially. If a policy returns **Reject**, the transaction is reverted immediately and remaining policies are not evaluated. +- If all policies return **Allow**, or if no policy explicitly rejects, the `desired_default_allow` setting on the [target](/ace/guides/policy-manager/manage-targets#default-allow-behavior) determines the outcome. + +For detailed information on composing effective rulesets, see [Policy Ordering & Composition](/ace/concepts/policy-ordering). + +## View protections + +## Manage an existing protection + +From the **Functions** or **Policies** view on your target contract (see [View protections](#view-protections) above), click on a policy instance to open a detail drawer. From there you can: + +- **Detach policy** — removes the protection so the policy no longer evaluates this function (see [Archive a protection](#archive-a-protection) below). +- **Edit instance** — opens the policy instance configuration (see [Update policy configuration](/ace/guides/policy-manager/manage-policies#update-policy-configuration)). + +### Extend a protection to additional chains + +If you created a protection on one chain and later want it to apply on additional chains, you can extend it via the API. + +### Archive a protection + +Archiving a protection unbinds the policy from the function. Once archived, the policy no longer evaluates transactions on that function. + + + +## Archival dependency chain + +ACE enforces an ordered archival flow. You must archive resources from the outside in: + +1. **Target protections** — archive these first +2. **Policy instances** — archive after all protections referencing them are archived +3. **Targets** — archive after all protections on the target are archived +4. **Policy engines** — archive after all policies in the engine are archived + +## Related pages + +- [Policy Ordering & Composition](/ace/concepts/policy-ordering) — how evaluation order affects transaction outcomes +- [Managing Policy Engines](/ace/guides/policy-manager/manage-engines) — create the engine that manages policies +- [Managing Targets](/ace/guides/policy-manager/manage-targets) — register contracts to protect +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — create and configure policy instances +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema + +--- + +# Managing Data Validators +Source: https://docs.chain.link/ace/guides/policy-manager/manage-data-validators +Last Updated: 2026-07-17 + +A **Data Validator** is an on-chain contract that inspects the **contents** of a credential — not just whether it exists. Attaching a Data Validator to an identity-validation policy lets you enforce rules on credential data, such as "only allow investors whose credential says they are in the US or Canada" or "reject any account whose credential country is on a sanctions list". + +This guide covers creating, configuring, and attaching Data Validators. For the credential side of the workflow — linking a data schema to a credential type and issuing credentials with data — see [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types#typed-credentials-with-data-schemas) and [Managing Credentials](/ace/guides/identity-manager/manage-credentials#issue-a-credential-with-data). + + + +## How Data Validators fit in + +Identity-validation policies — the [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) and the [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) — resolve a caller's address to a CCID and check credentials from **credential sources**. Each credential source can optionally reference a Data Validator. + +When a credential source has a Data Validator configured, the policy performs an extra step at transaction time: + +1. Resolve the account's CCID and confirm the credential exists (attestation check). +2. Fetch the credential's stored `credentialData`. +3. Call the Data Validator's `validateCredentialData(...)`, which returns `true` or `false`. + +The credential passes only if **both** the attestation check and the data check succeed. Without a Data Validator, the source is attestation-only — it confirms the credential exists but ignores its contents. + + + +## The AllowDenyList Data Validator + +ACE provides a pre-built, audited Data Validator implementation: the **AllowDenyList Data Validator**. It validates a credential payload against an **allowlist** and a **denylist**, with an optional restriction by credential type. Its rules are: + +- If the **denylist** contains any value present in the credential, validation **fails**. +- If the **allowlist** is non-empty, at least one value in the credential must be allowlisted; otherwise validation **fails**. +- If the allowlist is empty, the allow check passes (deny-only mode). + +The first use case shipped on top of this implementation is **jurisdiction control** using [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes (e.g., `US`, `CA`, `GB`). The country codes are the values checked against the allow and deny lists. + + + +## Prerequisites + +Before creating a Data Validator: + +1. A [policy engine](/ace/guides/policy-manager/manage-engines) deployed on your target chains. +2. A credential type linked to a **data schema** so its credentials carry data — for the jurisdiction use case, the ISO 3166-1 alpha-2 country code schema. See [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types#typed-credentials-with-data-schemas). +3. Credentials issued **with data** against that credential type. See [Managing Credentials](/ace/guides/identity-manager/manage-credentials#issue-a-credential-with-data). + +## Create a Data Validator + +A **Data Validator instance** is a deployed copy of a Data Validator implementation (such as the AllowDenyList country-code validator), configured with your specific allow and deny lists and scoped to one or more chains — the same shape as a policy instance. + +The AllowDenyList (country codes) Data Validator implementation ID is: + +```text +2aed366a-38af-4f48-b8e2-8fd1489db9fa +``` + +Create a Data Validator instance with a `POST` request. Provide the implementation ID and, for each chain, the `initial_config` with your allow and deny lists: + +```bash +curl -X POST https://ace.api.chain.link/v1/data-validators \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "Jurisdiction allow/deny", + "description": "Allow US and CA, deny KP", + "data_validator_implementation_id": "2aed366a-38af-4f48-b8e2-8fd1489db9fa", + "onchain_data_validators": [ + { + "chain_selector": "16015286601757825753", + "initial_config": { + "allowlist": [{ "item": "US" }, { "item": "CA" }], + "denylist": [{ "item": "KP" }], + "supportedDataTypes": [] + } + } + ] + }' +``` + +| Field | Required | Description | +| ---------------------------------- | -------- | ------------------------------------------------------------------------- | +| `name` | Yes | Human-readable name for the instance | +| `description` | No | Description of the instance's purpose | +| `data_validator_implementation_id` | Yes | UUID of the Data Validator implementation to instantiate | +| `onchain_data_validators` | Yes | Array of per-chain deployments with `chain_selector` and `initial_config` | + +Each on-chain Data Validator starts in `creation_pending` status until deployment completes. The response includes the instance `id` and the on-chain addresses per chain. + + + +## Update a Data Validator configuration + +You can update the allow and deny lists after deployment without redeploying the validator. Configuration changes use JSON Patch and are version-checked per chain for optimistic concurrency. + +Update the configuration with a `PATCH` request. Supply `on_chains` with the `current_config_version` for each chain you are changing: + +```bash +curl -X PATCH https://ace.api.chain.link/v1/data-validators//configs \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "patches": [ + { "op": "add", "path": "/allowlist/-", "value": "GB" } + ], + "on_chains": [ + { "chain_selector": "16015286601757825753", "current_config_version": "0" } + ] + }' +``` + +The JSON Patch format follows [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902). If the `current_config_version` does not match the on-chain state, the request is rejected — re-fetch the instance and retry with the current version. + +## Attach a Data Validator to a credential source + +A Data Validator takes effect only when it is referenced by a **credential source** on an identity-validation policy. Each credential source has a `dataValidator` field: + +- `0x0000000000000000000000000000000000000000` — attestation-only (default). The source checks only that the credential exists. +- A Data Validator address — the source additionally validates credential contents through that validator. + +Set the `dataValidator` field to your deployed Data Validator address when configuring the credential source on your [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) or [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) instance. See [Managing Policies — Update policy configuration](/ace/guides/policy-manager/manage-policies#update-policy-configuration) for how to change a policy instance's configuration. + + + +## View Data Validators + +List all Data Validators: + +```bash +curl https://ace.api.chain.link/v1/data-validators \ + -H "Authorization: Apikey " +``` + +| Parameter | Description | +| ---------------------------------- | ------------------------------------ | +| `page` | Page number (default: 1) | +| `page_size` | Results per page | +| `include_onchains` | Include per-chain deployment details | +| `data_validator_implementation_id` | Filter by implementation | +| `chain_selector` | Filter by chain | +| `address` | Filter by on-chain address | +| `status` | Filter by on-chain status | + +To retrieve a specific Data Validator by ID: + +```bash +curl https://ace.api.chain.link/v1/data-validators/ \ + -H "Authorization: Apikey " +``` + +## Archive a Data Validator + +Archiving a Data Validator deactivates the instance. Before archiving, detach it from any credential source that references it (set that source's `dataValidator` back to the zero address). + +Archive a Data Validator with a `PATCH` request: + +```bash +curl -X PATCH https://ace.api.chain.link/v1/data-validators/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "status": "archived" + }' +``` + +## Related pages + +- [Cross-Chain Identity — Credential data and privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy) — attestation-only vs. Data Validator checks +- [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types) — link a data schema to a credential type +- [Managing Credentials](/ace/guides/identity-manager/manage-credentials) — issue credentials with data +- [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) — the policy that consumes Data Validators via credential sources +- [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) — grouped identity validation with routing and Data Validators +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema + +--- + +# Custom Policies +Source: https://docs.chain.link/ace/guides/policy-manager/custom-policies +Last Updated: 2026-07-17 + +In addition to the [pre-built Policy Library](/ace/reference/policy-library), you can write and deploy **your own policy contract** and register it with the ACE Platform. Once registered, a custom policy behaves exactly like a library policy — you create instances of it, configure them, and attach them to protected functions. + +A custom policy implementation is **private to your organization**: it appears in your Policy Manager alongside the global library, but other organizations do not see it. + + + +## How it fits together + +A custom policy follows the same [implementation vs. instance](/ace/guides/policy-manager/manage-policies#policy-implementations-vs-policy-instances) model as library policies: + +1. **Write** a policy contract that implements the `IPolicy` interface. +2. **Deploy** it — this is your policy **implementation** contract — on each chain where you need it. +3. **Register** the implementation with the ACE Platform, providing its on-chain addresses and a **config schema**. This makes it an org-scoped policy type. +4. **Create instances** from it and **attach** them to target functions, exactly like a library policy. + +At instance-creation time, ACE's on-chain `PolicyFactory` clones your implementation into an instance and initializes it. The factory verifies that your implementation declares support for `IPolicy` (via ERC-165) — a contract that does not implement `IPolicy` cannot be instantiated. + +## Prerequisites + +- Solidity development experience and a deployment toolchain (Foundry, Hardhat, etc.). +- Familiarity with [Policy Management](/ace/concepts/policy-management) (the execution model, `run`/`postRun`, extractors, and parameters) and [Policy Ordering & Composition](/ace/concepts/policy-ordering). +- A deployed [PolicyEngine](/ace/guides/policy-manager/manage-engines). +- The `@chainlink/policy-management` contracts available in your project. + +## Step 1: Write the policy contract + +Every policy inherits from the base `Policy` contract and implements `run`. The base contract provides ownership, upgradeability, ERC-165 support, and the binding to a PolicyEngine. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Policy} from "@chainlink/policy-management/core/Policy.sol"; +import {IPolicyEngine} from "@chainlink/policy-management/interfaces/IPolicyEngine.sol"; + +contract LockoutPolicy is Policy { + string public constant override typeAndVersion = "LockoutPolicy 1.0.0"; + + mapping(address => uint256) public lockoutExpiresAt; + + /// @notice Configuration setter — locks an address for a duration (seconds). + function setLockout(address account, uint256 duration) public onlyOwner { + lockoutExpiresAt[account] = block.timestamp + duration; + } + + /// @notice Authorize setLockout so the PolicyEngine can apply configuration changes. + function authorizeConfigSelector(bytes4 selector) public pure override returns (bool) { + return selector == this.setLockout.selector; + } + + function run( + address, /* caller */ + address, /* subject */ + bytes4, /* selector */ + bytes[] calldata parameters, + bytes calldata /* context */ + ) public view override returns (IPolicyEngine.PolicyResult) { + // Always validate the inputs your policy expects. + require(parameters.length == 1, "LockoutPolicy: expected 1 parameter"); + address recipient = abi.decode(parameters[0], (address)); + + if (lockoutExpiresAt[recipient] > block.timestamp) { + revert IPolicyEngine.PolicyRejected("LockoutPolicy: address is locked out"); + } + return IPolicyEngine.PolicyResult.Continue; + } +} +``` + +Key pieces: + +- **`run(...)`** — read-only evaluation returning `Continue` (defer to the next policy), `Allowed` (approve and skip the rest of the chain), or reverting with `PolicyRejected` to block the transaction. The `parameters` array holds the extractor outputs mapped to this policy; always validate its length and decode defensively. +- **`postRun(...)`** *(optional)* — override it to mutate state after a successful check (for example, incrementing a counter). It is `onlyPolicyEngine` and is not called when the policy rejects. +- **`configure(bytes)`** *(optional)* — override it to decode initial configuration passed at instance creation. The base `initialize` calls it. +- **Configuration setters + `authorizeConfigSelector`** — expose owner-callable setters (like `setLockout`) to reconfigure the policy after deployment, and override `authorizeConfigSelector` to return `true` for those selectors so the PolicyEngine is allowed to call them. Selectors you do not authorize can only be called by the owner directly, not through the platform. +- **`typeAndVersion`** — a human-readable identifier, e.g. `"LockoutPolicy 1.0.0"`. + + + +## Step 2: Deploy the implementation + +Deploy your policy contract on each chain where you intend to use it. This deployed contract is the **implementation** — ACE clones it into instances; you do not attach the implementation to functions directly. Record the deployed address per chain; you need them in the next step. + + + +## Step 3: Register the implementation + +Register the deployed implementation with the Coordinator API so the platform can manage it. Provide a name, description, the on-chain addresses, and a **config schema**. + +```bash +curl -X POST https://ace.api.chain.link/v1/policy-implementations \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "name": "Lockout Policy", + "description": "Blocks transfers to locked-out recipients for a period of time", + "onchain_policy_implementations": [ + { "chain_selector": "16015286601757825753", "address": "0xYourImplementationOnSepolia" } + ], + "policy_config_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "lockouts": { + "type": "array", + "description": "Accounts to lock out and for how long.", + "items": { + "type": "object", + "required": ["account", "duration"], + "properties": { + "account": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" }, + "duration": { "type": "integer" } + } + }, + "metadata": { + "display_hints": { "network_behaviour": "apply_per_chain", "title": "Lockouts" }, + "primary_key_fields": ["account"], + "on_chain_operations": [ + { + "type": "add", + "function_abi": { + "name": "setLockout", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { "name": "account", "type": "address" }, + { "name": "duration", "type": "uint256" } + ], + "outputs": [] + } + } + ] + } + } + }, + "policy_run_parameters": [ + { "name": "Recipient", "type": "address", "max": 1 } + ], + "initial_configs": [] + } + }' +``` + +| Field | Required | Description | +| -------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `name` | Yes | Human-readable name shown in Policy Manager | +| `description` | Yes | What the policy does | +| `policy_config_schema` | Yes | JSON Schema describing configurable fields, the parameters the policy consumes, and how configuration maps to on-chain setters (see below) | +| `onchain_policy_implementations` | No | Array of `{ chain_selector, address }` for your deployed implementation on each chain. ACE records these; it does **not** deploy the implementation for you. | + +The registered implementation is created with type `custom` and scoped to your organization. It now appears in `GET /policy-implementations` alongside the global library. + +## The config schema + +The `policy_config_schema` is a [JSON Schema (draft-07)](https://json-schema.org/draft-07) document with three ACE-specific parts. It drives the Platform UI, validates the configuration you supply, and tells the platform how to translate configuration into on-chain calls. + +### `properties` — configurable fields + +Each property is a configurable field of your policy. Its `metadata.on_chain_operations` map configuration changes to your contract's setter functions: + +- `add` / `remove` — for list-style fields (add or remove an entry), pointing at setters like `setLockout`. +- `replace` — for scalar fields (set a single value), pointing at a setter like `setMax`. + +Each operation carries the `function_abi` of the setter to call. **Those setters must be authorized by your contract's `authorizeConfigSelector`** — otherwise the PolicyEngine cannot call them and configuration changes will fail. + +### `policy_run_parameters` — what the policy consumes + +An ordered array declaring the parameters your `run` function expects, each with a `name`, a Solidity `type`, and a `max`: + +- `max: 1` — exactly one value at that position. +- `max: -1` — a variable number of values (must be the last parameter). Use this for policies that check an arbitrary number of addresses. + +When you attach the policy to a function, the extractor outputs you map to it must match these parameters by type and position. See [Policy Management — the extractor and mapper pattern](/ace/concepts/policy-management#the-extractor-and-mapper-pattern). + +### `initial_configs` — what is set at creation + +An array of property names that are provided when an instance is **created** (in the instance's `initial_config`) rather than configured afterward. Leave it empty to configure everything after deployment. + +## Step 4: Create and use instances + +From here, a custom policy is used exactly like a library policy: + +1. [Create a policy instance](/ace/guides/policy-manager/manage-policies#create-a-policy-instance) from your implementation, supplying an `initial_config` that matches your config schema. ACE clones your implementation through the `PolicyFactory` and initializes the instance. +2. [Attach the instance to a protected function](/ace/guides/policy-manager/manage-protections), mapping the extractor outputs to your `policy_run_parameters`. +3. [Update the configuration](/ace/guides/policy-manager/manage-policies#update-policy-configuration) over time through the authorized config selectors. + +## Manage a custom implementation + +- **Update** name, description, or on-chain addresses with `PUT /policy-implementations/{id}`. +- **Archive** with `PATCH /policy-implementations/{id}` (`{"status":"archived"}`). All instances of the implementation must be archived first. + +## Security considerations + +A custom policy runs inside the policy chain of every function it protects, so a bug or malicious construct affects those transactions. In particular: + +- Keep `run` read-only and defensive — validate `parameters` length and decode carefully. +- Ensure any external calls cannot revert the whole chain unexpectedly; return a decision rather than propagating failures. +- Treat `postRun` state changes with the same care as any state-changing external function (reentrancy, access control). + +See [Security Considerations](/ace/guides/policy-manager/contracts/security-considerations) and the [Security Model](/ace/concepts/security) for the full trust model. + +## Related pages + +- [Custom Policies Tutorial](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/CUSTOM_POLICIES_TUTORIAL.md) — end-to-end contract walkthrough with a boilerplate template +- [Policy Management](/ace/concepts/policy-management) — execution model, `run`/`postRun`, parameters +- [Policy Management Contracts](/ace/reference/policy-management-contracts) — `IPolicy`, `IPolicyEngine`, and other interfaces +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — create and configure policy instances +- [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) — attach policies to functions +- [Security Considerations](/ace/guides/policy-manager/contracts/security-considerations) — trust boundaries and defensive patterns + +--- + +# Offchain Policies +Source: https://docs.chain.link/ace/guides/policy-manager/offchain-policies +Last Updated: 2026-07-17 + +ACE offchain policies evaluate data or logic outside the blockchain before authorizing a protected onchain action. The authorization is delivered onchain as a permit that is bound to a specific transaction intent. + +ACE supports two offchain policy models: + +- **Managed offchain policies** provide an out-of-the-box workflow. You configure the risk rules and protections, while Chainlink manages the CRE workflow, external provider call, onchain validator deployment, and permit delivery. The current implementation supports wallet risk screening with TRM Wallet Screening. +- **Custom offchain policies** support bespoke compliance logic and providers. You host the policy endpoint and work with Chainlink to configure and operate the integration. + + + +## Managed offchain policy guides + +- [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) — meet the TRM and CRE prerequisites, configure wallet risk rules, create the policy, and attach protections to target functions. +- [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) — call the Evaluation API, monitor an evaluation, and submit the protected transaction after its permit is ready onchain. +- [Granting Evaluation Access](/ace/guides/policy-manager/offchain-policies/grant-evaluation-access) — let another organization request permit evaluations against your protected target contract. + +## Learn how offchain policies work + +See [Offchain Policies](/ace/concepts/off-chain-policies) for the architecture and execution flows of both managed and custom offchain policy models. + +--- + +# Managing Offchain Policies (MVP) +Source: https://docs.chain.link/ace/guides/policy-manager/offchain-policies/manage-offchain-policies +Last Updated: 2026-07-17 + +ACE managed offchain risk policies screen wallet addresses with [TRM Wallet Screening](https://www.trmlabs.com/blockchain-intelligence-platform/wallet-screening) before allowing a protected onchain action. You configure the risk rules and the target functions to protect. Chainlink manages the CRE workflow, deploys the onchain permit validator, calls TRM, and delivers approved permits onchain. + +This guide covers policy setup. To integrate permit requests into your application, see [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits). + + + +## How the managed policy works + +Creating a managed offchain policy provisions two components: + +- A managed CRE workflow that screens the configured wallet addresses with TRM Wallet Screening. +- A [CertifiedActionDONValidatorPolicy](/ace/reference/policy-library/certified-action-don-validator-policy) (CADV) contract on each selected chain. The workflow writes approved permits to this contract through the Keystone Forwarder. + +When you attach the policy to a target function, the CADV becomes part of that function's policy chain. A call without a matching permit is rejected. A permit is valid only for its caller, target, function, and extracted parameters. + +## Prerequisites + +Before creating a managed offchain risk policy, you need: + +1. An ACE organization and [ACE API key](/ace/getting-started/account-setup#3-create-an-api-key). +2. A [policy engine](/ace/guides/policy-manager/manage-engines) deployed on every chain where you want to use the policy. +3. A [target contract](/ace/guides/policy-manager/manage-targets) associated with that policy engine. +4. An extractor attached to the policy engine that supports the target function. The extractor outputs determine which transaction parameters the permit must match. +5. A Chainlink CRE account with the [CRE CLI](https://docs.chain.link/cre/getting-started/cli-installation/macos-linux) installed and authenticated. +6. A TRM Labs account with Wallet Screening API access and a valid API key. ACE does not provide a TRM account or API credentials. See [TRM Wallet Screening](https://www.trmlabs.com/blockchain-intelligence-platform/wallet-screening) to learn about the product and request access. + +## Store the TRM credential in Vault DON + +The managed workflow retrieves your TRM credential from Vault DON at runtime. The credential remains encrypted and is not included in the offchain policy configuration. + +TRM uses HTTP Basic authentication with the API key as both the username and password. Before uploading it, encode `:` as Base64 without a trailing newline: + +```bash +export TRM_API_KEY="" +export TRM_BASIC_AUTH=$(printf '%s:%s' "$TRM_API_KEY" "$TRM_API_KEY" | base64 | tr -d '\n') +``` + +Create a secrets file that maps the Vault DON secret identifier to the environment variable: + +```yaml +secretsNames: + trmApiKey: + - TRM_BASIC_AUTH +``` + +Upload the secret using the CRE CLI. Replace `` with your CRE target: + +```bash +cre secrets create production-secrets.yaml \ + --target \ + --secrets-auth=browser +``` + +The identifier under `secretsNames` is the value to use for `secret_name` when you create the policy. In this example, it is `trmApiKey`. + +For prerequisites, authentication options, secret lifecycle operations, and troubleshooting, see [Using Secrets with Deployed Workflows](https://docs.chain.link/cre/guides/workflow/secrets/using-secrets-deployed). + + + +## Configure the risk policy + +The `wallet_risk_scoring` policy supports the following configuration: + +| Field | Required | Description | +| -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `secret_name` | Yes | Vault DON identifier containing the Base64-encoded TRM Basic Auth credential. | +| `addresses_to_check` | Yes | Which addresses to screen: `CALLER`, `PARAMETERS`, or `ALL`. | +| `risk_threshold` | Yes | Reject an address whose highest TRM risk level is at or above this threshold: `LOW`, `MEDIUM`, `HIGH`, or `SEVERE`. | +| `block_unknown` | No | When `true`, reject an address whose TRM risk level is `UNKNOWN`. Defaults to `false`. | +| `category_filters` | No | Category-specific thresholds. Each entry contains `category` and an optional `threshold`. If omitted, the global `risk_threshold` applies to that category. | +| `fail_mode` | No | `CLOSED` fails the evaluation when TRM returns an unsuccessful HTTP response. `OPEN` allows it to continue. Defaults to `CLOSED`. | + +### Select addresses to screen + +The `addresses_to_check` setting controls which addresses are sent to TRM: + +| Value | Addresses screened | +| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CALLER` | Only `caller_address` from the evaluation request. | +| `PARAMETERS` | Addresses found in `permit_parameters`. The first permit parameter represents the sender; subsequent address values are identified from the function signature. | +| `ALL` | The caller and all addresses found in `permit_parameters`, with duplicates removed. | + +For an ERC-20 `transfer(address,uint256)` evaluation with permit parameters `[from, to, amount]`, `CALLER` screens `from`, while `PARAMETERS` and `ALL` screen both `from` and `to`. + +The workflow accepts at most ten unique addresses per evaluation. + +### Apply global and category thresholds + +TRM assigns an overall risk level to each address. ACE orders the levels as follows: + +```text +UNKNOWN < LOW < MEDIUM < HIGH < SEVERE +``` + +An address is rejected when its overall level meets or exceeds `risk_threshold`. For example, a `HIGH` threshold rejects `HIGH` and `SEVERE` results. + +You can also apply different thresholds to individual TRM risk categories. The following configuration rejects: + +- Any address with an overall risk level of `HIGH` or `SEVERE`. +- Any `Sanctions` indicator at `LOW` or above. +- Any `Darknet Market` indicator at `MEDIUM` or above. + +```json +{ + "risk_threshold": "HIGH", + "block_unknown": false, + "category_filters": [ + { "category": "Sanctions", "threshold": "LOW" }, + { "category": "Darknet Market", "threshold": "MEDIUM" } + ] +} +``` + +Category names are matched case-insensitively against the categories returned by TRM. Consult your TRM Wallet Screening account for the categories available to your organization. + + + +## Create the offchain policy + +Create the policy with `POST /v1/policies`. Use the same policy engine and chains as the target you plan to protect: + +```bash +curl -X POST https://ace.api.chain.link/v1/policies \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "policy_kind": "offchain", + "name": "Transaction wallet screening", + "type": "wallet_risk_scoring", + "policy_engine_id": "", + "onchain_policies": [ + { "chain_selector": "" } + ], + "config": { + "secret_name": "trmApiKey", + "addresses_to_check": "ALL", + "fail_mode": "CLOSED", + "risk_threshold": "HIGH", + "block_unknown": false, + "category_filters": [ + { "category": "Sanctions", "threshold": "LOW" }, + { "category": "Darknet Market", "threshold": "MEDIUM" } + ] + } + }' +``` + +ACE allows one active offchain policy per organization. Creating another returns a conflict until the existing policy is archived. + +Policy creation is asynchronous. The initial response includes the policy ID and a `deployment_status` such as `pending` or `deploying`. Poll the policy until it becomes `active`: + +```bash +curl https://ace.api.chain.link/v1/policies/ \ + -H "Authorization: Apikey " +``` + +ACE creates a managed CRE workflow and deploys one CADV contract per selected chain. When the policy becomes active, `action_validators` contains each chain selector and CADV address. + + + +## Attach the policy to a target function + +A protection connects the managed policy to a function on your target. The `extractor_output_ids` must identify, in order, the values that the permit will bind to onchain. + +For `transfer(address,uint256)`, use the `from`, `to`, and `amount` outputs from the same `ERC20TransferExtractor`: + +```bash +curl -X POST https://ace.api.chain.link/v1/targets//protections \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "policy_kind": "offchain", + "policy_instance_id": "", + "function_signature": "transfer(address,uint256)", + "desired_position": 0, + "extractor_output_ids": [ + "", + "", + "" + ], + "onchain_target_protections": [ + { "chain_selector": "" } + ] + }' +``` + +The selected chains must be a subset of the chains configured on the offchain policy. The target and policy must also belong to the same policy engine. + +Protection attachment is asynchronous and returns `202 Accepted`. Poll the policy's protections until the new protection becomes `active`: + +```bash +curl https://ace.api.chain.link/v1/policies//protections \ + -H "Authorization: Apikey " +``` + +Once active, calls to the protected function require a matching permit. Continue with [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits). + +## Update the policy configuration + +Updating the configuration redeploys the managed workflow but does not replace its CADV contracts or protections: + +```bash +curl -X PUT https://ace.api.chain.link/v1/policies//config \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "config": { + "secret_name": "trmApiKey", + "addresses_to_check": "ALL", + "fail_mode": "CLOSED", + "risk_threshold": "SEVERE", + "block_unknown": true, + "category_filters": [ + { "category": "Sanctions", "threshold": "LOW" } + ] + } + }' +``` + +The policy enters `config_updating` and returns to `active` after the workflow is redeployed. Do not request new evaluations while the configuration is updating. + +## Remove a protection or policy + +Remove a protection before archiving its policy: + +```bash +curl -X DELETE \ + https://ace.api.chain.link/v1/policies//protections/ \ + -H "Authorization: Apikey " +``` + +The removal is asynchronous. After all protections are removed, archive the policy: + +```bash +curl -X PATCH https://ace.api.chain.link/v1/policies/ \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "policy_kind": "offchain", + "status": "archived" + }' +``` + +Archiving removes the managed workflow and its event watchers. It also allows the organization to create a new offchain policy. + +## Beta and MVP limitations + +- `wallet_risk_scoring` is the only managed offchain policy type. +- Each organization can have one active offchain policy. +- Each evaluation can screen at most ten unique addresses. +- Every permit is single-use (`maxUses = 1`) and does not expire (`expiry = 0`). These values are not configurable in the current release. +- The values in `permit_parameters` must match the outputs configured on the protection and the values extracted from the eventual onchain call. +- General CRE service limits also apply. See [CRE Service Quotas](https://docs.chain.link/cre/service-quotas). + +## Related pages + +- [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) — integrate evaluations and permits into an application +- [Off-Chain Policy Execution](/ace/concepts/off-chain-policies) — conceptual overview of managed and custom offchain policies +- [CertifiedActionDONValidatorPolicy](/ace/reference/policy-library/certified-action-don-validator-policy) — onchain permit validation +- [Protecting Target Functions](/ace/guides/policy-manager/manage-protections) — protection concepts and evaluation order +- [Coordinator API Reference](/api/ace/coordinator/docs) — policy and protection API schemas + +--- + +# Requesting Offchain Permits +Source: https://docs.chain.link/ace/guides/policy-manager/offchain-policies/request-offchain-permits +Last Updated: 2026-07-17 + +After a managed offchain risk policy protects a function, the function rejects calls that do not have a matching permit. Your application must request an evaluation, wait for the permit to be stored onchain, and then submit the protected transaction. + +This guide uses an ERC-20 `transfer(address,uint256)` as the example. For policy and protection setup, see [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies). + + + +## Evaluation flow + +1. Your application describes the intended transaction to the ACE Evaluation API. +2. ACE triggers the managed CRE workflow for your organization. +3. The workflow extracts the configured addresses and screens them with TRM Wallet Screening. +4. If the risk rules reject any address, the evaluation becomes `rejected` and no permit is created. +5. If the risk rules pass, the workflow writes a permit to the CADV contract through the Keystone Forwarder. +6. After ACE observes the onchain `PermitStored` event, the evaluation becomes `ready`. +7. Your application submits the protected transaction with the same caller, target, function, and parameter values. +8. The policy engine finds and consumes the permit. The permit cannot authorize another transaction. + +## Prerequisites + +Before requesting an evaluation, verify that: + +- The offchain policy has `deployment_status: active`. +- Its protection for the target function has `status: active`. +- You know the target contract address and chain selector. +- You know the wallet that will submit the onchain transaction. It must be the same address as `caller_address`. +- You know the ordered extractor outputs configured on the protection. Your `permit_parameters` must use that same order. + +## Evaluation API + +The production Evaluation API base URL is: + +```text +https://ace.api.chain.link/v1/evaluation +``` + +It uses the same ACE API key as the Coordinator API: + +```http +Authorization: Apikey +``` + + + +## Construct the evaluation request + +Start an evaluation with `POST /evaluate`: + +```json +{ + "caller_address": "0x1111111111111111111111111111111111111111", + "subject": "0x2222222222222222222222222222222222222222", + "function_signature": "transfer(address,uint256)", + "parameters": { + "to": "0x3333333333333333333333333333333333333333", + "amount": "100" + }, + "permit_parameters": [ + "0x0000000000000000000000001111111111111111111111111111111111111111", + "0x0000000000000000000000003333333333333333333333333333333333333333", + "0x0000000000000000000000000000000000000000000000000000000000000064" + ], + "chain_selector": "", + "unique_evaluation_id": "transfer-018f6b3e-7c42-7a1f-a8ed-5ecf90c03b30" +} +``` + +| Field | Description | +| ---------------------- | ---------------------------------------------------------------------------------------------------------- | +| `caller_address` | Wallet that will submit the protected transaction. | +| `subject` | Address of the protected target contract. | +| `function_signature` | Canonical function signature, such as `transfer(address,uint256)`. Do not send the four-byte selector. | +| `parameters` | Structured representation of the function arguments. ACE stores it with the evaluation as contextual data. | +| `permit_parameters` | Ordered ABI-encoded values used for address screening and exact onchain permit matching. | +| `chain_selector` | Chain where the target, policy engine, protection, and CADV are deployed. | +| `unique_evaluation_id` | Client-generated identifier unique to this transaction intent. ACE uses it to derive the permit ID. | + +### Encode permit parameters + +Each `permit_parameters` item is a `0x`-prefixed, 32-byte ABI word. The items must have the same order as the `extractor_output_ids` on the protection. + +For `transfer(address,uint256)`, the `ERC20TransferExtractor` produces: + +```text +[from, to, amount] +``` + +Therefore, encode: + +1. `from`: the transaction caller, as an ABI `address`. +2. `to`: the transfer recipient, as an ABI `address`. +3. `amount`: the transfer amount, as an ABI `uint256`. + +Use a standard ABI library rather than concatenating untrusted values manually. For example, with ethers v6: + +```javascript +import { AbiCoder } from "ethers" + +const abiCoder = AbiCoder.defaultAbiCoder() + +const permitParameters = [ + abiCoder.encode(["address"], [callerAddress]), + abiCoder.encode(["address"], [recipientAddress]), + abiCoder.encode(["uint256"], [amount]), +] +``` + + + +### Choose a unique evaluation ID + +`unique_evaluation_id` is scoped to your ACE organization. ACE combines it with the organization ID to derive a deterministic `permit_id`. + +Retrying with the same `unique_evaluation_id` is idempotent: ACE returns the existing evaluation instead of triggering another workflow execution. Never reuse an ID for a different caller, target, function, or set of parameters. + +Use a UUID or another collision-resistant identifier generated by your backend. Store it with the transaction intent so you can safely recover from a lost HTTP response. + +## Start the evaluation + +```bash +curl -X POST https://ace.api.chain.link/v1/evaluation/evaluate \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d @evaluation.json +``` + +The response contains the deterministic permit ID and initial status: + +```json +{ + "permit_id": "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "status": "evaluating" +} +``` + +The response does not mean that the transaction is approved. Wait until the evaluation becomes `ready`. + +## Poll the evaluation + +Retrieve the evaluation using the returned permit ID: + +```bash +curl \ + https://ace.api.chain.link/v1/evaluation/evaluate/ \ + -H "Authorization: Apikey " +``` + +Polling every five seconds is a reasonable default. Stop when the evaluation reaches a terminal status. + +| Status | Terminal | Meaning | +| ------------ | -------- | ----------------------------------------------------------------------------------- | +| `evaluating` | No | The workflow is screening the configured addresses. | +| `approving` | No | TRM checks passed and the workflow is publishing the permit onchain. | +| `ready` | Yes | The permit was stored onchain. The protected transaction can now be submitted. | +| `rejected` | Yes | At least one configured risk rule rejected the evaluation. No permit was created. | +| `error` | Yes | The evaluation or onchain permit publication failed. No usable permit is available. | + +For `rejected` and `error`, the response can include a `reason`. `workflow_execution_id` identifies the CRE execution when available. Because permits do not expire in the current release, `expires_at` is normally `null`. + +```json +{ + "permit_id": "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "status": "ready", + "reason": null, + "workflow_execution_id": "", + "expires_at": null +} +``` + +## Retry an evaluation + +The CRE HTTP trigger allows one new execution per workflow every 60 seconds. Polling an existing evaluation does not trigger the workflow and is not subject to that trigger rate. + +- If the initial HTTP response is lost or ambiguous, retry `POST /evaluate` with the same `unique_evaluation_id`. ACE returns the existing evaluation if it was created. +- If an evaluation reaches `rejected`, changing the identifier alone does not change the policy decision. Review the risk result or transaction intent. +- If an evaluation reaches `error` and the underlying issue is resolved, wait at least 60 seconds and submit a new evaluation with a new `unique_evaluation_id`. + +See [CRE Service Quotas](https://docs.chain.link/cre/service-quotas) for current workflow limits. + +## Submit the protected transaction + +Submit the transaction only after the evaluation becomes `ready`. The sender must be `caller_address`, and the target function must receive values that produce the same extracted parameters as `permit_parameters`. + +No permit bytes are added to the transaction. The CADV already stores the permit and looks it up from the action's caller, target, selector, and extracted parameters. + +After the protected call succeeds, the CADV increments the permit's usage counter. Managed risk policy permits have `maxUses = 1`, so another transaction with the same intent requires a new evaluation and permit. + + + +## Troubleshooting + +### Evaluation is rejected + +- At least one address met or exceeded the global `risk_threshold`. +- A TRM risk indicator met or exceeded a configured category threshold. +- TRM returned `UNKNOWN` and `block_unknown` is enabled. + +Review the response `reason` and the policy configuration. Do not retry a rejected intent without understanding why it was rejected. + +### Evaluation returns an error + +- The Vault DON secret identifier does not match `secret_name`. +- The TRM credential was not encoded as `:` before Base64 encoding. +- TRM or the CRE confidential HTTP request failed. +- The selected chain does not have an active CADV for the policy. +- The workflow could not write the permit onchain. + +If a TRM HTTP error should allow the action, review the policy's `fail_mode`. Use `OPEN` only after assessing the compliance impact. + +### Evaluation is ready but the transaction reverts + +- The transaction sender differs from `caller_address`. +- The target address or function differs from the evaluation. +- The eventual transaction produces different extractor values than `permit_parameters`. +- The protection's extractor outputs are missing or ordered differently. +- The permit has already been consumed. +- Another policy in the target function's policy chain rejected the call. + +## Related pages + +- [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) — configure TRM screening and attach protections +- [Granting Evaluation Access](/ace/guides/policy-manager/offchain-policies/grant-evaluation-access) — let another organization request evaluations against your target +- [Off-Chain Policy Execution](/ace/concepts/off-chain-policies) — conceptual overview +- [CertifiedActionDONValidatorPolicy](/ace/reference/policy-library/certified-action-don-validator-policy) — how permits are stored and consumed onchain +- [Evaluation API Reference](/api/ace/evaluation/docs) — complete request, response, and error schemas +- [CRE Service Quotas](https://docs.chain.link/cre/service-quotas) — current CRE workflow limits + +--- + +# Granting Evaluation Access +Source: https://docs.chain.link/ace/guides/policy-manager/offchain-policies/grant-evaluation-access +Last Updated: 2026-07-17 + +By default, only the organization that owns a target contract can request offchain permit evaluations for it through the [Evaluation API](/ace/guides/policy-manager/offchain-policies/request-offchain-permits). **Evaluation access grants** let you extend this capability to other organizations — for example, allowing a DEX or lending protocol to request permits against your token's compliance rules. + +When you grant evaluation access, the grantee organization can call the Evaluation API for the specified target and offchain policy. The evaluation runs against **your** managed CRE workflow and risk configuration — the grantee does not need its own offchain policy or TRM credential. + + + +## Roles and concepts + +| Term | Meaning | +| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Grantor** | The organization that **owns** the target contract and the offchain policy, and grants evaluation access. | +| **Grantee** | The organization that **receives** evaluation access. It can call the Evaluation API for the specified target and policy. | +| **Access grant** | The link between an offchain policy–target pair and a grantee organization. It is either `active` or `revoked`. | +| **Org ID** | The identifier of an organization. The grantee shares theirs with the grantor so the grantor can create the grant. Retrieve it with `GET /organizations/me` (Coordinator API). | + +## What the grantee can and cannot do + +An active evaluation access grant lets the grantee: + +- **Call the Evaluation API** (`POST /evaluate`) for the granted target and offchain policy. The evaluation uses the grantor's managed workflow and TRM configuration. +- **Poll evaluation status** (`GET /evaluate/{permitId}`) for evaluations the grantee started. +- **List granted targets** using `GET /targets?include_granted=true` to discover targets other organizations have shared with them. + +The grantee **cannot**: + +- Modify the offchain policy, its risk thresholds, or the protection configuration. +- Manage the target contract, its policy engine, or any other resource owned by the grantor. +- Re-share evaluation access with a third organization. + + + +## Prerequisites + +Before granting evaluation access: + +1. You have a [managed offchain policy](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) with `deployment_status: active`. +2. The offchain policy has an active [protection](/ace/guides/policy-manager/manage-protections) on the target function. +3. You know the grantee's **Org ID**. Ask them to retrieve it: + +```bash +# Run by the grantee +curl https://ace.api.chain.link/v1/organizations/me \ + -H "Authorization: Apikey " +``` + +## Grant evaluation access + +As the target and policy owner, create the grant by specifying the offchain policy ID, target ID, and the grantee's Org ID: + +```bash +curl -X POST https://ace.api.chain.link/v1/policies//targets//access-grants \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "grantee_org_id": "" + }' +``` + +The response is the created grant: + +```json +{ + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "grantee_org_id": "org-456", + "grantor_org_id": "org-123", + "status": "active", + "granted_at": 1800000000, + "revoked_at": null +} +``` + +## View who has access + +List the active and past grants for a specific offchain policy and target pair: + +```bash +curl https://ace.api.chain.link/v1/policies//targets//access-grants \ + -H "Authorization: Apikey " +``` + +Each entry includes the grantee, the status (`active` or `revoked`), and timestamps, giving you an audit trail of who was granted access and when. + +## Discover granted targets (grantee) + +As a grantee, include `include_granted=true` when listing targets to see targets other organizations have shared with you, alongside your own: + +```bash +curl "https://ace.api.chain.link/v1/targets?include_granted=true" \ + -H "Authorization: Apikey " +``` + +Once you can see a granted target, you can call the Evaluation API for it the same way you would for your own targets. See [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) for the full evaluation workflow. + +## Revoke access + +As the policy and target owner, revoke a grant by setting its status to `revoked`: + +```bash +curl -X PATCH \ + https://ace.api.chain.link/v1/policies//targets//access-grants/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "status": "revoked" + }' +``` + +Revocation takes effect immediately. The grantee can no longer request evaluations for this target and policy. The grant record is retained with a `revoked_at` timestamp for audit purposes. To restore access later, create a new grant. + +## Related pages + +- [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) — call the Evaluation API and submit the protected transaction +- [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) — configure TRM screening and attach protections +- [External Registries](/ace/guides/identity-manager/external-registries) — a similar grant model for sharing identity and credential registries +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema + +--- + +# Identity Manager Guides +Source: https://docs.chain.link/ace/guides/identity-manager +Last Updated: 2026-04-06 + +These guides cover the day-to-day operations of an Identity Manager — from setting up registries to issuing and managing credentials across chains. + + + +## Available guides + +- [Managing Registries](/ace/guides/identity-manager/manage-registries) — create, view, import, and archive identity and credential registry pairs +- [Managing Identities](/ace/guides/identity-manager/manage-identities) — register cross-chain identities (CCIDs), map wallet addresses across chains, and manage identity lifecycle +- [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types) — define the categories of credentials your registry supports (e.g., KYC, accreditation, sanctions clearance) +- [Managing Credentials](/ace/guides/identity-manager/manage-credentials) — issue, renew, revoke, and set expiration on credentials using the attestation model +- [External Registries](/ace/guides/identity-manager/external-registries) — share a registry with another organization, and use registries shared with you as credential sources + +--- + +# Managing Registries +Source: https://docs.chain.link/ace/guides/identity-manager/manage-registries +Last Updated: 2026-04-06 + +## What are registries? + +A **registry** in ACE is the top-level organizational unit for the Identity Manager. Each registry bundles two types of sub-registries: + +- **Identity registries** — map wallet addresses to [Cross-Chain Identifiers (CCIDs)](/ace/concepts/cross-chain-identity#the-cross-chain-identifier-ccid). +- **Credential registries** — manage the lifecycle of credentials linked to CCIDs. + +Each sub-registry corresponds to a smart contract deployed on a specific blockchain. A single registry can span multiple chains by including sub-registries on each target network. For a deeper explanation of the registry model and how it fits into the identity lifecycle, see [Cross-Chain Identity](/ace/concepts/cross-chain-identity). + +## Create a registry + +## View registries + +## Multi-chain setup + +Registries are designed to work across multiple chains. Each entry in `identity_registries` and `credential_registries` targets a specific `chain_selector`, and the platform deploys (or imports) a contract on each specified chain independently. + +A typical multi-chain configuration: + +- **Identity registries** on every chain where users interact — so the IdentityRegistry on each chain can resolve wallet addresses to CCIDs locally. +- **Credential registries** on every chain where policies need to verify credentials at runtime. + +Because CCIDs are chain-agnostic identifiers, a credential issued on one chain's CredentialRegistry is logically valid across all chains. The multi-chain deployment ensures that each chain has a local copy of the registry contracts for low-latency, on-chain lookups. For more on how this model works, see [Cross-Chain Identity](/ace/concepts/cross-chain-identity). + + + +## Update a registry + +You can update a registry's name, description, and add new sub-registries. Updates are **additive** — you can add new identity or credential sub-registries to additional chains, but you cannot remove existing sub-registry pairs. + +## Share across organizations + +You can grant another organization read access to a registry you own, so it can use your identities and credentials as a credential source without re-issuing them. Grants are read-only for the recipient and can be revoked at any time. For the full workflow — granting, discovering registries shared with you, using them, and revoking — see [External Registries](/ace/guides/identity-manager/external-registries). + +## Archive a registry + +Archiving a registry deactivates it and prevents any further operations. Before archiving, all identities associated with the registry must be removed or archived first. + + + +## Related pages + +- [Cross-Chain Identity](/ace/concepts/cross-chain-identity) — conceptual background on CCIDs, registries, and credential sources +- [Managing Identities and Credentials](/ace/guides/identity-manager/manage-identities) — register identities and issue credentials within a registry +- [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types) — define and manage the credential types used in credential registries +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema and parameters + +--- + +# Managing Identities +Source: https://docs.chain.link/ace/guides/identity-manager/manage-identities +Last Updated: 2026-04-06 + +This guide covers how to register, view, update, and archive cross-chain identities (CCIDs) using the ACE Platform UI or the Coordinator API. Identities are the foundation of ACE's credential system — every credential is issued against an identity. + + + +## What are identities (CCIDs)? + +A **cross-chain identity (CCID)** aggregates multiple wallet addresses across EVM chains into a single logical entity. Rather than treating each address on each chain as a separate user, ACE maps them all to one CCID. Credentials issued against that CCID are then valid for every linked address on every chain — no re-issuance or bridging required. + +Each identity includes: + +- **Title** — A human-readable label for internal use only (e.g., "Jane Doe"). This value is never written on-chain. +- **Entity ID** — A unique external identifier that ties the identity back to your system of record (e.g., a KYC provider user ID). This value must be unique within a registry. +- **Registry** — The registry the identity belongs to. +- **On-chain identities** — One or more wallet address + chain selector pairs that map to this CCID on-chain. + +For a deeper look at how CCIDs work, how they are generated, and the privacy considerations involved, see [Cross-Chain Identity](/ace/concepts/cross-chain-identity). + +## Register an identity + +## Bulk import identities + +When onboarding many users at once, use the batch endpoint to create multiple identities in a single atomic request. Each identity in the batch follows the same schema as the single-create endpoint, including the optional `credentials` array — so you can register identities and issue credentials in one call. + +This feature is **API-only**. In the Platform UI, the **Bulk import via API** option under **+ Add identity** links to this documentation. + +Send a `POST` request to `/identities/batch`: + +```bash +curl -X POST "https://ace.api.chain.link/v1/identities/batch" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "identities": [ + { + "title": "Identity A", + "entity_id": "user-001", + "registry_id": "", + "onchain_identities": [ + { + "address": "0x1111111111111111111111111111111111111111", + "chain_selector": "16015286601757825753" + } + ], + "credentials": [ + { + "credential_type_id": "", + "expires_at": 1800000000 + } + ] + }, + { + "title": "Identity B", + "entity_id": "user-002", + "registry_id": "", + "onchain_identities": [ + { + "address": "0x2222222222222222222222222222222222222222", + "chain_selector": "16015286601757825753" + }, + { + "address": "0x3333333333333333333333333333333333333333", + "chain_selector": "3478487238524512106" + } + ] + } + ] + }' +``` + +The `credentials` array is optional on each identity. The second identity in this example is created without credentials. + + + +## View and search identities + +## Update an identity + +You can update an identity's title, description, and on-chain address mappings. ACE offers two update approaches: full replacement and partial update. + +## Cross-chain identity mapping + +A single CCID can span as many chains and addresses as needed. This is the core value proposition of ACE's identity model: one credential verification applies everywhere. + +For example, an entity operating wallets on Ethereum Sepolia, Arbitrum Sepolia, and Base Sepolia would have a single identity with three on-chain mappings: + +```bash +curl -X POST "https://ace.api.chain.link/v1/identities" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Multi-Chain Operator", + "entity_id": "operator-xyz-007", + "registry_id": "a1b2c3d4-5678-9abc-def0-1234567890ab", + "onchain_identities": [ + { + "chain_selector": "16015286601757825753", + "address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + { + "chain_selector": "3478487238524512106", + "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + { + "chain_selector": "10344971235874465080", + "address": "0xcccccccccccccccccccccccccccccccccccccccc" + } + ] + }' +``` + +| Chain | Chain Selector | Address | +| :--------------- | :--------------------- | :-------------- | +| Ethereum Sepolia | `16015286601757825753` | `0xaaaa...aaaa` | +| Arbitrum Sepolia | `3478487238524512106` | `0xbbbb...bbbb` | +| Base Sepolia | `10344971235874465080` | `0xcccc...cccc` | + +All three addresses resolve to the same CCID. A credential issued against this identity — such as a KYC attestation — is valid for all three addresses across all three chains. When any of these addresses interacts with a policy-protected contract, the policy resolves the address to the shared CCID and checks credentials from there. + +To add or remove chains later, use the [full update (PUT)](#full-update-put) endpoint with the updated list of on-chain identities. + +## Archive an identity + +Archiving marks an identity as inactive. Archived identities are retained for audit purposes but are no longer considered active. + + + +--- + +# Managing Credential Types +Source: https://docs.chain.link/ace/guides/identity-manager/manage-credential-types +Last Updated: 2026-07-17 + +Credential types define the categories of attestations you can issue to [cross-chain identities (CCIDs)](/ace/concepts/cross-chain-identity). Each credential type represents a distinct kind of verification — for example, KYC completion, accredited investor status, or sanctions clearance. When you create a credential type, the `credential_type` string you provide is hashed to produce a `credential_type_hash` that policy contracts reference on-chain. + +Credential types are scoped to a specific credential registry. Before creating credential types, make sure your [registries are set up](/ace/guides/identity-manager/manage-registries). + +A credential type can optionally be linked to a **data schema**, which lets the credentials you issue against it carry structured data (for example, a jurisdiction code). See [Typed credentials with data schemas](#typed-credentials-with-data-schemas) below. + +## What is a credential type? + +A credential type is a string you define to represent a specific compliance check or verification. This string is hashed and registered on-chain, so it cannot be changed after creation. You can create any credential types that match your requirements — for example: + +| Credential type string | Use case | +| ---------------------- | -------------------------------------- | +| `PROOF_OF_IDENTITY` | Identity verification | +| `PROOF_OF_FUNDS` | Source of funds or reserves check | +| `AML_CHECK` | Anti-money-laundering screening result | + +The `credential_type` string is case-sensitive and must be unique within a registry. + +## Define a credential type + +Register a credential type with a `POST` request: + +```bash +curl -X POST "https://ace.api.chain.link/v1/credential-types" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "registry_id": "", + "title": "KYC Verification", + "credential_type": "KYC", + "description": "Basic Know Your Customer identity verification" + }' +``` + +The response includes the generated `credential_type_id` and the `credential_type_hash` derived from your `credential_type` string. + +## Understand credential type hashes + +When you create a credential type, ACE hashes the `credential_type` string to produce a deterministic `credential_type_hash`. This hash is what gets written on-chain and what policy contracts use when evaluating identity-based rules. + +```text +credential_type string → credential_type_hash → on-chain reference +"KYC" → 0x7a8b...3f21 → used by policy contracts +``` + +Because the hash is derived from the string, choosing your `credential_type` strings carefully matters — they cannot be changed after creation. Policy contracts such as the [Credential Registry Identity Validator](/ace/reference/policy-library/credential-registry-identity-validator-policy) reference credentials by their `credential_type_hash` when checking whether an identity holds a required attestation. + + + +## Typed credentials with data schemas + +By default, credentials are **attestation-only**: they record that an identity holds a credential of a given type, with no additional data. You can instead create a **typed** credential type by linking it to a **data schema**. Credentials issued against a typed credential type carry structured data (validated against the schema), which policies can then evaluate through a [Data Validator](/ace/guides/policy-manager/manage-data-validators). + +A **data schema** is a reusable definition of the shape and format of a credential's data. ACE provides shared, ready-to-use schemas — the first is an [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code schema for jurisdiction use cases (an array of two-letter country codes such as `US`, `CA`, `GB`). + +To make a credential type typed, pass a `data_schema_id` when you create it. The ISO 3166-1 alpha-2 country code data schema ID is: + +```text +fb786cd7-6397-4ac6-790c-35746f343cad +``` + +```bash +curl -X POST "https://ace.api.chain.link/v1/credential-types" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "registry_id": "", + "title": "Jurisdiction", + "credential_type": "common.country", + "description": "Holder jurisdiction as ISO 3166-1 alpha-2 country codes", + "data_schema_id": "fb786cd7-6397-4ac6-790c-35746f343cad" + }' +``` + +Once a credential type is linked to a data schema, every credential you issue against it **must** include `credential_data` matching that schema — see [Issue a credential with data](/ace/guides/identity-manager/manage-credentials#issue-a-credential-with-data). + + + +## View credential types + +List credential types with a `GET` request. Use the `registry_id` query parameter to filter by registry: + +```bash +curl "https://ace.api.chain.link/v1/credential-types?registry_id=&page=1&page_size=25" \ + -H "Authorization: Apikey " +``` + +To retrieve a single credential type by ID: + +```bash +curl "https://ace.api.chain.link/v1/credential-types/" \ + -H "Authorization: Apikey " +``` + +## Update a credential type + +You can update a credential type's **title** and **description**. The `credential_type` string and `credential_type_hash` cannot be changed. + +Update a credential type with a `PUT` request: + +```bash +curl -X PUT "https://ace.api.chain.link/v1/credential-types/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "title": "KYC Verification (Enhanced)", + "description": "Enhanced KYC verification including document and liveness checks" + }' +``` + +You can also make partial updates with a `PATCH` request: + +```bash +curl -X PATCH "https://ace.api.chain.link/v1/credential-types/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "description": "Updated description for KYC verification" + }' +``` + +## Archive a credential type + +Archiving a credential type prevents new credentials of that type from being issued. Existing credentials remain valid until they are individually archived or expire. + + + +Archive a credential type with a `PATCH` request: + +```bash +curl -X PATCH "https://ace.api.chain.link/v1/credential-types/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "status": "archived" + }' +``` + +If active credentials still reference the type, the request returns an error. Archive all associated credentials first, then retry. + +## Related resources + +- [Cross-Chain Identity](/ace/concepts/cross-chain-identity) — conceptual overview of CCIDs, registries, and credential types +- [Managing Credentials](/ace/guides/identity-manager/manage-credentials) — issue, revoke, and manage credentials linked to CCIDs +- [Managing Registries](/ace/guides/identity-manager/manage-registries) — view and manage identity and credential registry deployments +- [Credential Registry Identity Validator Policy](/ace/reference/policy-library/credential-registry-identity-validator-policy) — the policy contract that checks credentials on-chain + +--- + +# Managing Credentials +Source: https://docs.chain.link/ace/guides/identity-manager/manage-credentials +Last Updated: 2026-07-17 + +Credentials are attestations that a [cross-chain identity (CCID)](/ace/concepts/cross-chain-identity) holds a specific qualification — for example, KYC verification, accredited investor status, or sanctions clearance. Each credential links a **credential type** to an **identity** and is recorded on-chain across every chain where the credential registry is deployed. + +This guide covers the full credential lifecycle: issuing, viewing, updating, expiring, and revoking credentials through the Coordinator API. + +## Attestation vs. typed credentials + +ACE supports two kinds of credentials: + +- **Attestation-only** (the default) — The credential records only that an identity holds a credential of a given type, with no additional data. When you issue one, the on-chain record contains just the **credential type hash**, the **identity** (CCID) it is issued to, and an **issuance timestamp**. Policies verify existence — for example, "does this address have a valid KYC credential?" — without accessing any personally identifiable information (PII). +- **Typed** — When the credential type is linked to a [data schema](/ace/guides/identity-manager/manage-credential-types#typed-credentials-with-data-schemas), the credential also carries structured `credential_data` (for example, a jurisdiction code). Policies can then evaluate the contents through a [Data Validator](/ace/guides/policy-manager/manage-data-validators), not just the credential's existence. + +In both cases, no PII should be stored on-chain — credential data must be a minimal, non-sensitive value (such as an ISO country code) or a hash. For a deeper discussion of credential data and privacy, see [Credential Data and Privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy). + +## Issue a credential + +To issue a credential, you need a registered [identity](/ace/guides/identity-manager/manage-identities) and at least one [credential type](/ace/guides/identity-manager/manage-credential-types) defined in your registry. + + + +Issue a credential with a `POST` request: + +```bash +curl -X POST "https://ace.api.chain.link/v1/credentials" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "credential_type_id": "", + "identity_id": "", + "external_unique_id": "kyc-2026-04-acme", + "expires_at": 1806883200 + }' +``` + +| Field | Required | Description | +| -------------------- | -------- | ------------------------------------------------------------ | +| `credential_type_id` | Yes | UUID of the credential type to issue | +| `identity_id` | Yes | UUID of the target identity (CCID) | +| `external_unique_id` | No | Your own reference identifier for this credential | +| `expires_at` | No | Unix timestamp (integer); omit for a non-expiring credential | + +## Issue a credential with data + +When the credential type is linked to a [data schema](/ace/guides/identity-manager/manage-credential-types#typed-credentials-with-data-schemas), include a `credential_data` field. The value must match the schema — for the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code schema, that is a JSON array of two-letter country codes. ACE validates the data against the schema and encodes it on-chain. + +```bash +curl -X POST "https://ace.api.chain.link/v1/credentials" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "credential_type_id": "", + "identity_id": "", + "credential_data": ["US"], + "external_unique_id": "jurisdiction-2026-04-acme", + "expires_at": 1806883200 + }' +``` + + + +Once issued, the credential data can be enforced at transaction time by attaching a [Data Validator](/ace/guides/policy-manager/manage-data-validators) to the credential source of an identity-validation policy. + +## Issue credentials during identity creation + +You can issue credentials inline when registering a new identity by including a `credentials` array in the `POST /identities` request body. This is useful when you have completed verification before registration and want to create the identity and its credentials in a single call. + +```bash +curl -X POST "https://ace.api.chain.link/v1/identities" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Acme Corp Treasury", + "entity_id": "acme-corp-001", + "registry_id": "", + "onchain_identities": [ + { + "chain_selector": "16015286601757825753", + "address": "0x1234567890abcdef1234567890abcdef12345678" + } + ], + "credentials": [ + { + "credential_type_id": "", + "external_unique_id": "kyc-2026-04-acme", + "expires_at": 1806883200 + }, + { + "credential_type_id": "" + } + ] + }' +``` + +Each entry in the `credentials` array follows the same schema as the standalone `POST /credentials` endpoint, except that `identity_id` is inferred from the identity being created. See [Managing Identities](/ace/guides/identity-manager/manage-identities) for the full identity creation reference. + +## View and filter credentials + +List credentials with a `GET` request. All query parameters are optional: + +```bash +curl "https://ace.api.chain.link/v1/credentials?credential_type_id=&page=1&page_size=25" \ + -H "Authorization: Apikey " +``` + +| Parameter | Description | +| -------------------- | --------------------------------------------------- | +| `credential_type_id` | Filter by credential type | +| `identity_id` | Filter by identity | +| `entity_id` | Filter by entity | +| `registry_id` | Filter by registry | +| `include_onchains` | Include on-chain deployment details in the response | +| `page` | Page number (default: 1) | +| `page_size` | Results per page | + +To retrieve a single credential by its ID: + +```bash +curl "https://ace.api.chain.link/v1/credentials/" \ + -H "Authorization: Apikey " +``` + +## Credential expiration + +The `expires_at` field controls whether a credential has a limited validity period. + +- **No expiration** — Omit `expires_at` when issuing. The credential remains valid indefinitely until explicitly archived. +- **With expiration** — Provide a Unix timestamp (integer). Once the timestamp passes, policy checks that require this credential type will treat the credential as invalid. + +To **renew** an expiring credential, update it with a new `expires_at` value (see the next section). Alternatively, you can archive the expired credential and issue a new one. + + + +## Update a credential + +You can update a credential's `external_unique_id` and `expires_at` fields. You **cannot** change the credential type or the associated identity — to change either, archive the credential and issue a new one. + +Update a credential with a `PUT` request: + +```bash +curl -X PUT "https://ace.api.chain.link/v1/credentials/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "external_unique_id": "kyc-2026-04-acme-renewed", + "expires_at": 1838419200 + }' +``` + +| Field | Required | Description | +| -------------------- | -------- | ------------------------------------------------- | +| `external_unique_id` | Yes | Updated reference identifier | +| `expires_at` | No | New expiration timestamp; omit to leave unchanged | + +You can also perform a partial update with `PATCH`. The `PATCH` endpoint accepts `external_unique_id` and `expires_at` independently, but you **cannot** combine field updates with a status change in the same request: + +```bash +curl -X PATCH "https://ace.api.chain.link/v1/credentials/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "expires_at": 1838419200 + }' +``` + +## Revoke (archive) a credential + +Archiving a credential removes it from the on-chain credential registry. After archival, policy contracts will no longer see this credential — any policy that requires it (such as the [Credential Registry Identity Validator](/ace/reference/policy-library/credential-registry-identity-validator-policy)) will reject transactions from the associated addresses. + +Common reasons to revoke a credential: + +- KYC verification expired or failed re-verification +- Sanctions status changed +- Accreditation lapsed +- Entity relationship terminated + +Archive a credential with a `PATCH` request: + +```bash +curl -X PATCH "https://ace.api.chain.link/v1/credentials/" \ + -H "Authorization: Apikey " \ + -H "Content-Type: application/json" \ + -d '{ + "status": "archived" + }' +``` + + + +## Related resources + +- [Cross-Chain Identity](/ace/concepts/cross-chain-identity) — CCID model, credential registries, and the attestation lifecycle +- [Managing Identities](/ace/guides/identity-manager/manage-identities) — register and manage CCIDs and their on-chain address mappings +- [Managing Credential Types](/ace/guides/identity-manager/manage-credential-types) — create and organize the credential categories your registry supports +- [Credential Registry Identity Validator Policy](/ace/reference/policy-library/credential-registry-identity-validator-policy) — the policy that checks credentials at transaction time +- [Managing Data Validators](/ace/guides/policy-manager/manage-data-validators) — enforce rules on credential data at transaction time +- [Beta Scope](/ace/beta-scope) — current scope and limitations + +--- + +# External Registries +Source: https://docs.chain.link/ace/guides/identity-manager/external-registries +Last Updated: 2026-07-17 + +**External registries** let one organization reuse another organization's [registry](/ace/guides/identity-manager/manage-registries) without re-issuing identities or credentials. The registry owner grants a second organization **read access**, and that organization can then reference the registry's identities and credentials — for example, to enforce KYC in its own policies using a KYC provider's registry. + +Access is granted per registry, is **read-only** for the recipient, and can be revoked at any time. + +## Roles and concepts + +| Term | Meaning | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Grantor** | The organization that **owns** the registry and grants access to it. | +| **Grantee** | The organization that **receives** read access to the registry. | +| **Access grant** | The link between a registry and a grantee organization. It is either `active` or `revoked`. | +| **Org ID** | The identifier of an organization. The grantee shares theirs with the grantor so the grantor can grant access. Retrieve it with `GET /organizations/me` (Coordinator API). | +| **`access_type`** | A field on a registry indicating whether the caller `owned` it or was `granted` access to it. | + +## What the grantee can and cannot do + +An active grant gives the grantee **read access** to the registry: + +- **Can** list and view the registry, and read its [credential types](/ace/guides/identity-manager/manage-credential-types), [identities](/ace/guides/identity-manager/manage-identities), and [credentials](/ace/guides/identity-manager/manage-credentials). +- **Can** reference the registry's on-chain contracts as a credential source in its own [identity-validation policies](/ace/reference/policy-library/credential-registry-identity-validator-policy). +- **Cannot** write to the registry — registering identities, issuing credentials, or changing configuration remains exclusive to the grantor. + + + +## Grant access to another organization + +Granting access requires the grantee's **Org ID**. Ask the grantee to retrieve it and share it with you: + +```bash +# Run by the grantee — returns their organization, including its id +curl https://ace.api.chain.link/v1/organizations/me \ + -H "Authorization: Apikey " +``` + +As the registry owner, create the grant: + +```bash +curl -X POST https://ace.api.chain.link/v1/registries//access-grants \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "grantee_org_id": "org-456" + }' +``` + +The response is the created grant: + +```json +{ + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "grantee_org_id": "org-456", + "grantor_org_id": "org-123", + "status": "active", + "granted_at": 1800000000, + "revoked_at": null +} +``` + +## View who has access + +List the active and past grants for a registry you own: + +```bash +curl https://ace.api.chain.link/v1/registries//access-grants \ + -H "Authorization: Apikey " +``` + +Each entry includes the grantee, the status (`active` or `revoked`), and the `granted_at` / `revoked_at` timestamps, giving you an audit trail of who was granted access and when. + +## Use a registry shared with you + +As a grantee, include `include_granted=true` when listing registries to see registries other organizations have shared with you, alongside your own: + +```bash +curl "https://ace.api.chain.link/v1/registries?include_granted=true" \ + -H "Authorization: Apikey " +``` + +Each registry in the response carries an **`access_type`** field: + +- `"owned"` — your organization owns the registry. +- `"granted"` — another organization (shown in `org_id`) granted you access. + +Once you can see a granted registry, you use it the same way you would reference any credential source: + +1. Read the registry to get the on-chain **identity registry** and **credential registry** contract addresses per chain, and read its **credential types** to get the `credential_type_hash` values you need. +2. Add those addresses and credential type hashes as a **credential source** on your [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) or [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) instance. + +Your policy then validates credentials issued by the other organization at transaction time. Because the grant is read-only, you rely on the grantor to keep the credentials current; if they revoke a credential, your policy sees the change immediately. + + + +## Revoke access + +As the registry owner, revoke a grant by setting its status to `revoked`. Identify the grant by the grantee's Org ID: + +```bash +curl -X PATCH https://ace.api.chain.link/v1/registries//access-grants/org-456 \ + -H "Content-Type: application/json" \ + -H "Authorization: Apikey " \ + -d '{ + "status": "revoked" + }' +``` + +Revocation takes effect immediately. The grant record is retained with a `revoked_at` timestamp for audit purposes rather than deleted, so the history of grants and revocations is preserved. To restore access later, create a new grant. + +## What happens when access is revoked + +Revoking a registry access grant is a **platform-level action only** — it removes the registry from the grantee's view in the ACE Platform (UI and API). The grantee can no longer browse the registry, read its credentials, or reference it in new policy configurations. + +**Onchain, nothing changes automatically.** If the grantee's policies already reference the revoked registry's onchain contracts as a credential source, those policies continue to validate credentials from that registry at transaction time. The onchain policy contracts have no awareness of platform-level access grants — they only know the registry contract addresses that were configured as credential sources. + +### What each party should do + +**Grantor** — After revoking access, be aware that the grantee's existing policies may still reference your registry onchain. + +**Grantee** — After a grant is revoked, the ACE Platform displays a warning on any policy instance that references a source from the revoked registry. You should remove the revoked registry source from your policy configuration to ensure your compliance setup reflects the current state of your access agreements. Until you remove it: + +- The policy continues to validate credentials from the revoked registry onchain. +- You cannot edit the revoked source — you can only remove it. +- You cannot reference the revoked registry in new policy configurations. + + + +## Related pages + +- [Managing Registries](/ace/guides/identity-manager/manage-registries) — create and manage the registries you own +- [Cross-Chain Identity](/ace/concepts/cross-chain-identity) — CCIDs, registries, and credential sources +- [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) — reference a registry as a credential source +- [Managing Policies](/ace/guides/policy-manager/manage-policies) — configure policy instances and their credential sources +- [Coordinator API Reference](/api/ace/coordinator/docs) — full API schema + +--- + +# Policy Management Contracts +Source: https://docs.chain.link/ace/reference/policy-management-contracts +Last Updated: 2026-04-15 + + + +The Policy Management contracts handle on-chain policy enforcement for ACE-compatible contracts. The source code and full documentation are available in the policy-management package of the chainlink-ace repository (Business Source License 1.1). + +## Core interfaces + +| Interface | Description | +| :--------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [IPolicyEngine](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/interfaces/IPolicyEngine.sol) | Central orchestrator that manages policies, extractors, and mappers for protected contracts. Receives calls from `PolicyProtected` targets, runs the policy chain, and returns allow/reject decisions. | +| [IPolicyProtected](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/interfaces/IPolicyProtected.sol) | Base interface for any contract that wants policy enforcement. Provides the `runPolicy` modifier, the connection to a `PolicyEngine`, and context handling for passing off-chain data to policies. | +| [IPolicy](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/interfaces/IPolicy.sol) | Standard interface for all policy contracts. Each policy implements `run` (read-only evaluation that returns allow/continue/reject) and optionally `postRun` (state changes after execution, such as updating volume counters). | +| [IExtractor](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/interfaces/IExtractor.sol) | Parses transaction calldata into named parameters (e.g., `to` and `value` from an ERC-20 `transfer`) so policies can evaluate them. One extractor is registered per function signature. | +| [IMapper](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/interfaces/IMapper.sol) | Optional interface for transforming or combining extracted parameters before they reach a policy. Only needed for advanced scenarios where a policy expects a different parameter shape than the extractor provides. | + +## Pre-built policies + +ACE provides a library of audited, ready-to-use policy implementations covering common compliance scenarios — allowlists, volume limits, role-based access, pause controls, and more. See the policies source code for implementation details, or the [Policy Library](/ace/reference/policy-library) page for configuration and usage. + +## Reference token implementations + +The repository includes reference token contracts that demonstrate full ACE integration: + +- [ERC-20 Compliance Token](https://github.com/smartcontractkit/chainlink-ace/tree/main/packages/tokens/erc-20) + — A policy-protected ERC-20 with frozen token handling. +- [ERC-3643 Compliance Token](https://github.com/smartcontractkit/chainlink-ace/tree/main/packages/tokens/erc-3643) + — A compliant implementation of the ERC-3643 T-REX standard. + +## Repository documentation + +The policy-management docs folder contains detailed guides: + +- [Concepts](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/CONCEPTS.md) + — Architecture, policy flow, extractors, mappers, and context handling +- [API Guide](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/API_GUIDE.md) + — Task-oriented guide with code examples for common operations +- [API Reference](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/API_REFERENCE.md) + — Complete interface specifications with function signatures and events +- [Custom Policies Tutorial](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/CUSTOM_POLICIES_TUTORIAL.md) + — End-to-end walkthrough for building a custom policy contract +- [Policy Ordering Guide](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/POLICY_ORDERING_GUIDE.md) + — How evaluation order affects transaction outcomes +- [Security Considerations](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/docs/SECURITY.md) + — Trust model, gas considerations, and context handling + +## Related pages + +- [Architecture](/ace/concepts/architecture) — How PolicyEngine contracts fit into the ACE system +- [Policy Management](/ace/concepts/policy-management) — Conceptual overview of policy chains and evaluation +- [Making Your Contract ACE-Compatible](/ace/guides/policy-manager/contracts/ace-compatible) — What your contract needs to work with ACE +- [Policy Library](/ace/reference/policy-library) — Pre-built policy implementations with configuration details + +--- + +# Policy Library +Source: https://docs.chain.link/ace/reference/policy-library +Last Updated: 2026-07-17 + +ACE ships with a library of pre-built, audited policies that cover the most common compliance and access control patterns. Each policy is a standalone smart contract that plugs into a PolicyEngine and evaluates transactions at runtime. + +For guidance on combining policies and understanding execution order, see [Policy Ordering & Composition](/ace/concepts/policy-ordering). + +## Policy summary + +| Policy | Description | +| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| [AllowPolicy](/ace/reference/policy-library/allow-policy) | Maintains an allowlist. Rejects the transaction if any checked address is **not** on the list. | +| [BypassPolicy](/ace/reference/policy-library/bypass-policy) | Maintains an allowlist. If **all** checked addresses are on the list, immediately allows the transaction and **skips all remaining policies**. | +| [RejectPolicy](/ace/reference/policy-library/reject-policy) | Maintains a denylist. Rejects the transaction if any checked address **is** on the list. | +| [OnlyAuthorizedSenderPolicy](/ace/reference/policy-library/only-authorized-sender-policy) | Rejects the transaction if the sender (`msg.sender`) is not on the authorized list. | +| [RoleBasedAccessControlPolicy](/ace/reference/policy-library/role-based-access-control-policy) | Maps roles to function selectors. Rejects if the sender does not hold a role allowed for the called function. | +| [MaxPolicy](/ace/reference/policy-library/max-policy) | Rejects the transaction if the extracted value exceeds a configured maximum. | +| [VolumePolicy](/ace/reference/policy-library/volume-policy) | Rejects the transaction if the extracted value is below a minimum or above a maximum. | +| [VolumeRatePolicy](/ace/reference/policy-library/volume-rate-policy) | Tracks cumulative volume per account per time period. Rejects if the period's cap would be exceeded. | +| [SecureMintPolicy](/ace/reference/policy-library/secure-mint-policy) | Checks a Chainlink Proof of Reserve feed. Rejects if minting would push total supply beyond verified reserves. | +| [IntervalPolicy](/ace/reference/policy-library/interval-policy) | Divides time into repeating slot-based cycles. Rejects if the current slot is outside the allowed window. | +| [PausePolicy](/ace/reference/policy-library/pause-policy) | Global toggle. Rejects every transaction when paused; passes through when unpaused. | +| [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) | Checks each address against configured credential requirements. Rejects if any address lacks required credentials. | +| [GroupedIdentityValidatorPolicy](/ace/reference/policy-library/grouped-identity-validator-policy) | Routes each address to a credential group, then validates it against that group's requirements. Rejects if no group matches or requirements fail. | +| [CertifiedActionDONValidatorPolicy](/ace/reference/policy-library/certified-action-don-validator-policy) | Validates DON-issued permits delivered on-chain via the Keystone Forwarder. Rejects if no valid permit exists. | + +--- + +# AllowPolicy +Source: https://docs.chain.link/ace/reference/policy-library/allow-policy +Last Updated: 2026-03-31 + +The AllowPolicy restricts transactions to a known set of approved addresses. It checks every address extracted from the transaction against an allowlist and immediately rejects if any of them is not on the list, halting all subsequent policy checks. + +## Configuration + +### Address allowlist + +The allowlist defines which addresses are permitted to participate in transactions protected by this policy. The list starts empty at deployment and must be populated afterward — until you add at least one address, every transaction will be rejected. + +Each address is added or removed individually. When a protected function is called, the extractor provides one or more addresses from the transaction (for example, both the sender and receiver of a token transfer). Which addresses the policy receives depends on the [mapper configuration](/ace/concepts/policy-management#worked-example-erc-20-transfer). All of those addresses must be on the allowlist for the transaction to pass. + +## Runtime behavior + +The policy expects a variable number of parameters from the extractor, each an address. All provided addresses are checked against the allowlist. + +- **`run()`** — Reverts if *any* address is not on the allowlist. Returns `Continue` otherwise. +- **`postRun()`** — No state changes. + +## API reference + +### Setter functions + +- **`allowAddress(address account)`** — Adds an address to the allowlist. Reverts if the address is already listed. +- **`disallowAddress(address account)`** — Removes an address from the allowlist. Reverts if the address is not listed. + +### View functions + +- **`addressAllowed(address account)`** — Returns `true` if the address is on the allowlist. + +## Use cases + +- **Regulated access** — Restrict token transfers to a known set of approved addresses. +- **Gradual rollout** — Start with a small allowlist and expand as new addresses are vetted. + +## Source + +[AllowPolicy.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/policies/AllowPolicy.sol) + +--- + +# BypassPolicy +Source: https://docs.chain.link/ace/reference/policy-library/bypass-policy +Last Updated: 2026-03-31 + +The BypassPolicy gives privileged addresses a fast path through the policy chain. If *all* addresses extracted from the transaction are on the bypass list, the policy immediately allows the transaction and skips every remaining policy in the chain. If any address is not on the list, the policy returns `Continue` and lets subsequent policies decide. + +This is the only built-in policy that returns `Allowed`. + + + +## Configuration + +### Address allowlist + +The bypass list defines which addresses can skip the rest of the policy chain. The list starts empty at deployment and must be populated afterward. + +Each address is added or removed individually. When a protected function is called, the extractor provides one or more addresses from the transaction. Which addresses the policy receives depends on the [mapper configuration](/ace/concepts/policy-management#worked-example-erc-20-transfer). All of those addresses must be on the bypass list for the fast path to activate — if even one address is missing, the policy returns `Continue` and normal policy evaluation continues. + +## Runtime behavior + +The policy expects a variable number of parameters from the extractor, each an address. + +- **`run()`** — Returns `Allowed` if all provided addresses are on the bypass list, skipping all subsequent policies. Returns `Continue` otherwise. +- **`postRun()`** — No state changes. + +## API reference + +### Setter functions + +- **`allowAddress(address account)`** — Adds an address to the bypass list. Reverts if the address is already listed. +- **`disallowAddress(address account)`** — Removes an address from the bypass list. Reverts if the address is not listed. + +### View functions + +- **`addressAllowed(address account)`** — Returns `true` if the address is on the bypass list. + +## Use cases + +- **Privileged access** — Let administrators or system contracts bypass compliance checks entirely. +- **Layered permissions** — Place at the top of a policy chain so that listed addresses skip volume limits, identity checks, and other restrictions. + +## Source + +[BypassPolicy.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/policies/BypassPolicy.sol) + +--- + +# CertifiedActionDONValidatorPolicy +Source: https://docs.chain.link/ace/reference/policy-library/certified-action-don-validator-policy +Last Updated: 2026-07-17 + +The CertifiedActionDONValidatorPolicy (CADV) is the onchain contract that validates permits generated by [offchain policy execution](/ace/concepts/off-chain-policies). When a Chainlink DON workflow approves an action, it delivers a permit through the Keystone Forwarder. The CADV stores the permit and verifies it when the protected function is called. + +For a full explanation of how off-chain policies work, what you can connect to, and how the permit flow operates, see [Off-Chain Policy Execution](/ace/concepts/off-chain-policies). + + + +## Permit lifecycle + +Managed offchain permits are **pre-presented**: the DON writes the permit to the CADV before the user submits the protected transaction. The user does not include permit bytes in the transaction. + +The CADV indexes a permit by its transaction intent, which includes: + +- Caller address +- Protected target address +- Function selector +- Ordered parameters produced by the target function's extractor + +At execution time, the policy engine passes the actual caller, target, selector, and extracted parameters to the CADV. The call is allowed only when they match a stored, valid permit. After the protected call succeeds, the policy engine invokes `postRun`, which increments the permit's usage count and emits `PermitUsed`. + +The managed `wallet_risk_scoring` policy currently creates permits with: + +- `maxUses = 1` — the permit can authorize one successful transaction. +- `expiry = 0` — the permit does not expire. + +These values are fixed in the current Beta release. + +The CADV also emits `PermitStored` when the workflow publishes a permit. ACE waits for this event before changing the corresponding evaluation status to `ready`. + +## Combining with other policies + +The CADV is attached to a target function like any other policy. It can run before or after onchain policies such as identity, allowlist, or volume checks. All policies in the function's policy chain must allow the call. + +See [Policy Ordering & Composition](/ace/concepts/policy-ordering) for guidance on evaluation order. + +## Related pages + +- [Managing Offchain Policies (MVP)](/ace/guides/policy-manager/offchain-policies/manage-offchain-policies) +- [Requesting Offchain Permits](/ace/guides/policy-manager/offchain-policies/request-offchain-permits) +- [Off-Chain Policy Execution](/ace/concepts/off-chain-policies) + +## Source + +- [CertifiedActionDONValidatorPolicy.sol](https://github.com/smartcontractkit/chainlink-ace/blob/main/packages/policy-management/src/policies/CertifiedActionDONValidatorPolicy.sol) + +--- + +# CredentialRegistryIdentityValidatorPolicy +Source: https://docs.chain.link/ace/reference/policy-library/credential-registry-identity-validator-policy +Last Updated: 2026-07-17 + +The CredentialRegistryIdentityValidatorPolicy validates that accounts involved in a transaction hold the required credentials from ACE's [Cross-Chain Identity](/ace/concepts/cross-chain-identity) infrastructure. It checks each account against configured credential sources (IdentityRegistry + CredentialRegistry pairs) and credential requirements (which credential types must be present and how many validations are needed). + +This is the primary policy for enforcing identity-based compliance such as KYC, accreditation, or sanctions screening. + + + +## Configuration + +Both properties below can be set when the policy is first deployed and updated afterward by the policy owner. Credential sources contain on-chain addresses and must be configured per network. Credential requirements define rules that apply across chains. + +### Credential sources + +A credential source defines *where* to look up identity and credential data for a given credential type. Each source is a tuple of: + +- **Credential type ID** — A `bytes32` identifier for the credential type this source applies to (e.g., KYC, accreditation). +- **Identity registry address** — The IdentityRegistry contract that maps wallet addresses to Cross-Chain Identifiers (CCIDs). +- **Credential registry address** — The CredentialRegistry contract that stores credentials linked to CCIDs. +- **Data validator address** (optional) — A contract that performs additional validation on the credential data. Set to `address(0)` for attestation-only checks, or a [Data Validator](/ace/guides/policy-manager/manage-data-validators) address to validate credential contents (for example, a jurisdiction allow/deny list). See [Credential data and privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy) for a full explanation of attestation-only vs. Credential Data Validator checks. + +Multiple sources can be registered for the same credential type. The policy checks all configured sources and counts validations across them. Source uniqueness is determined by the `(identityRegistry, credentialRegistry)` pair — two sources with the same registry pair but different `dataValidator` addresses are considered duplicates. + +**Limits:** Up to 8 sources per credential type. + +### Credential requirements + +A credential requirement defines *what* credentials an account must hold. Each requirement specifies: + +- **Requirement ID** — A unique `bytes32` identifier for this requirement. +- **Credential type IDs** — An array of `bytes32` credential types to check (e.g., KYC, accreditation). +- **Minimum validations** — How many of the listed credential types must validate successfully. Must be at least 1. +- **Invert flag** — When `true`, the check passes if the credential does *not* exist. This is useful for "must not be sanctioned" checks, where you want the transaction to succeed only if the account does not hold a sanctions credential. + +An account passes a requirement if it accumulates at least `minValidations` successful validations across the listed credential types and configured sources. + +**Limits:** Up to 8 requirements total, up to 32 credential types per requirement. + +## Runtime behavior + +The policy expects a variable number of parameters from the extractor, each an address to validate. Every address is checked against all configured requirements. + +For each address, the validation process: + +1. Iterates through all credential requirements. +2. For each requirement, checks the listed credential types against each configured source. +3. For each source, looks up the account's CCID in the IdentityRegistry, then checks whether the CredentialRegistry holds the credential for that CCID. +4. If a DataValidator is configured, it additionally validates the credential data. +5. Counts successful validations. If the count meets `minValidations`, the requirement passes. + +- **`run()`** — Reverts if any address fails any requirement. Returns `Continue` if all addresses pass all requirements. +- **`postRun()`** — No state changes. + +## API reference + +### Setter functions + +**Credential sources:** + +- **`addCredentialSource(CredentialSourceInput input)`** — Adds a source for a credential type. Reverts if the source already exists or if the maximum number of sources (8) for that credential type has been reached. +- **`removeCredentialSource(bytes32 credentialTypeId, address identityRegistry, address credentialRegistry)`** — Removes a source. Reverts if the source is not found. + +**Credential requirements:** + +- **`addCredentialRequirement(CredentialRequirementInput input)`** — Adds a requirement. Reverts if a requirement with the same ID already exists or if the configuration is invalid. +- **`removeCredentialRequirement(bytes32 requirementId)`** — Removes a requirement. Reverts if the requirement ID is not found. ### View functions @@ -1612,7 +5263,7 @@ For each address, the validation process: # GroupedIdentityValidatorPolicy Source: https://docs.chain.link/ace/reference/policy-library/grouped-identity-validator-policy -Last Updated: 2026-08-18 +Last Updated: 2026-07-17 The GroupedIdentityValidatorPolicy validates transaction participants against **different credential requirements depending on who they are**. Instead of applying one fixed rule set to every account — as the [CredentialRegistryIdentityValidatorPolicy](/ace/reference/policy-library/credential-registry-identity-validator-policy) does — it first **routes** each account to a **group**, then validates the account against that group's requirements. This lets a single policy enforce, for example, one set of rules for individuals and another for businesses, or different rules per jurisdiction. @@ -1704,7 +5355,7 @@ A source tells the policy where to resolve identities and credentials for a give - **Credential type ID** — The `bytes32` credential type this source applies to. - **Identity registry address** — The IdentityRegistry that maps wallet addresses to CCIDs. - **Credential registry address** — The CredentialRegistry that stores credentials linked to CCIDs. -- **Data validator address** (optional) — A contract that performs additional validation on the credential data. Set to `address(0)` for attestation-only checks, or a Data Validator address to validate credential contents. See [Credential data and privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy). +- **Data validator address** (optional) — A contract that performs additional validation on the credential data. Set to `address(0)` for attestation-only checks, or a [Data Validator](/ace/guides/policy-manager/manage-data-validators) address to validate credential contents. See [Credential data and privacy](/ace/concepts/cross-chain-identity#credential-data-and-privacy). Source uniqueness within a group and credential type is determined by the `(identityRegistry, credentialRegistry)` pair. Both routing and requirements draw on the same per-group sources. @@ -1776,7 +5427,7 @@ For each address, the policy: # IntervalPolicy Source: https://docs.chain.link/ace/reference/policy-library/interval-policy -Last Updated: 2026-04-20 +Last Updated: 2026-03-31 The IntervalPolicy restricts transaction execution to specific time slots within a repeating cycle. It divides time into fixed-length slots, groups those slots into a cycle, and only allows transactions when the current slot falls within a configured range. Transactions attempted outside the allowed window are rejected. @@ -1867,7 +5518,7 @@ This policy does not use extracted parameters. It relies entirely on `block.time # MaxPolicy Source: https://docs.chain.link/ace/reference/policy-library/max-policy -Last Updated: 2026-04-20 +Last Updated: 2026-03-31 The MaxPolicy enforces a maximum value constraint on individual transactions. It compares a value extracted from the transaction (for example, a transfer amount) against a configured ceiling and rejects any transaction where the value exceeds that ceiling. @@ -1913,7 +5564,7 @@ The policy expects exactly one parameter from the extractor: # OnlyAuthorizedSenderPolicy Source: https://docs.chain.link/ace/reference/policy-library/only-authorized-sender-policy -Last Updated: 2026-04-20 +Last Updated: 2026-03-31 The OnlyAuthorizedSenderPolicy restricts who can call a protected function based on the transaction sender. Unlike the AllowPolicy and RejectPolicy (which check addresses extracted from the transaction parameters), this policy checks `msg.sender` directly and rejects if the sender is not on the authorized list. @@ -1962,7 +5613,7 @@ This policy does not use extracted parameters. It checks `msg.sender` directly. # PausePolicy Source: https://docs.chain.link/ace/reference/policy-library/pause-policy -Last Updated: 2026-04-20 +Last Updated: 2026-03-31 The PausePolicy provides a global pause/unpause mechanism for protected functions. When paused, the policy rejects every transaction regardless of any other conditions. When unpaused, it returns `Continue` and lets subsequent policies decide. @@ -2005,7 +5656,7 @@ This policy does not use extracted parameters. It checks only its internal pause # RejectPolicy Source: https://docs.chain.link/ace/reference/policy-library/reject-policy -Last Updated: 2026-04-20 +Last Updated: 2026-03-31 The RejectPolicy blocks transactions involving addresses on a denylist. It checks every address extracted from the transaction and immediately rejects if any of them is on the list, halting all subsequent policy checks. @@ -2047,7 +5698,7 @@ The policy expects a variable number of parameters from the extractor, each an a # RoleBasedAccessControlPolicy Source: https://docs.chain.link/ace/reference/policy-library/role-based-access-control-policy -Last Updated: 2026-04-20 +Last Updated: 2026-03-31 The RoleBasedAccessControlPolicy provides fine-grained, role-based access control for protected functions. It maps named roles to specific function selectors (operations) and checks whether the transaction sender holds a role that is permitted to perform the requested operation. If the sender lacks the required role, the transaction is rejected. @@ -2107,7 +5758,7 @@ This policy checks `msg.sender` against role assignments for the current functio # SecureMintPolicy Source: https://docs.chain.link/ace/reference/policy-library/secure-mint-policy -Last Updated: 2026-04-20 +Last Updated: 2026-03-31 The SecureMintPolicy ensures the total supply of a token does not exceed the actual reserves of the underlying asset. Before every mint, it reads the latest reserve value from a [Chainlink Proof of Reserve](/data-feeds/proof-of-reserve) data feed and checks whether the new total supply (current supply plus the requested mint amount) would exceed what the reserves can back. If it would, the policy rejects the transaction. @@ -2198,7 +5849,7 @@ The policy expects one parameter from the extractor: # VolumePolicy Source: https://docs.chain.link/ace/reference/policy-library/volume-policy -Last Updated: 2026-04-20 +Last Updated: 2026-03-31 The VolumePolicy enforces minimum and maximum value constraints on individual transactions. It compares a value extracted from the transaction against configured bounds and rejects if the value falls outside the allowed range. @@ -2260,7 +5911,7 @@ The policy expects exactly one parameter from the extractor: # VolumeRatePolicy Source: https://docs.chain.link/ace/reference/policy-library/volume-rate-policy -Last Updated: 2026-04-20 +Last Updated: 2026-03-31 The VolumeRatePolicy enforces per-account volume limits within configurable time periods. It tracks cumulative transaction amounts for each account and rejects transactions that would push the account's total volume past the allowed maximum for the current period. When a new period begins, the counter resets automatically. @@ -2331,7 +5982,7 @@ The policy expects exactly two parameters from the extractor: # Cross-Chain Identity Contracts Source: https://docs.chain.link/ace/reference/cross-chain-identity-contracts -Last Updated: 2026-08-18 +Last Updated: 2026-07-17