Token (CIP-0112)

The token package implements the Token Standard V2 interfaces from CIP-0112. It provides holdings, transfer instructions, allocations with batch settlement, allocation requests, registry rules, event logging, compliance hooks, and CIP-0086 allowances. Its closest Solidity analogue is a partial ERC20.

The package has no dependency on other OpenZeppelin packages. It depends on the Token Standard V2 interface DARs.

Experimental. Version 0.x, unaudited, and subject to a redesign before release. The upstream Token Standard V2 interfaces are at devnet stage. The interface DARs in dars/vendor/ are local builds from a pinned splice commit, and every package ID changes when upstream publishes a release. Source: experiments/token/tokenCIP112-v1 in OpenZeppelin/canton-contracts.

Modules

The package has no root module. Import the submodules that you need, for example:

import OpenZeppelin.TokenCIP112V1.Holding

Each module implements the matching Splice.Api.Token.*V2 interfaces.

ModuleTemplatesPurpose
.HoldingTokenHoldingAn asset holding, maintained jointly by the instrument admin and the account parties, with an optional lock.
.TransferTokenTransferInstructionThe transfer-instruction lifecycle: accept, reject, withdraw, and expire.
.AllocationTokenAllocation, BatchSettlementAuthorizationAllocations backed by locked holdings, and all-or-nothing batch settlement. A batch must exactly cover its allocations.
.AllocationRequestTokenAllocationRequestThe request from an application that a wallet turns into an allocation.
.AllowanceTokenAllowanceA CIP-0086 spending budget with ERC-20 approve and transferFrom behavior.
.RegistryTokenRulesThe registry rules: transfer, allocation, and settlement factories, plus mint, burn, and allowance choices.
.BaseTokenEventLogThe event log for holding changes.
.D1TrustedAttesterRegistry, ComplianceAttestation, SeizureOrderCompliance attestation (D1) and lawful-process seizure authority (D2).

The package defines no Daml interfaces or exceptions of its own, so it ships as one implementation package.

Authority

  • The instrument admin and the account parties jointly maintain holdings. TokenRules_Mint and TokenRules_Burn need both.
  • Wallets act through the Token Standard V2 interface choices. The choices check identity, funding, and expiry before they move value.
  • The application selects and discloses the canonical TokenRules contract for its instrument.
  • If allowedExecutors on TokenRules is None, any party can be named as a settlement executor. Set it when that matters for your deployment.

Compliance Hooks

  • D1 attestation. If requiredAttesterRegistryCid is set on TokenRules, every batch settlement must present a valid ComplianceAttestation from an attester in that TrustedAttesterRegistry.
  • D2 seizure. The admin can mark an allocation for seizure. A sweep of the locked holdings also needs a SeizureOrder from a party other than the admin.

Allowances

The owner approves a TokenAllowance for a spender with TokenRules_ApproveAllowance. The spender draws on it with TokenRules_TransferFrom, and the registry applies the current configuration.

  • A transfer to the spender's own account completes in one step. A transfer to another receiver creates a pending TokenTransferInstruction that the receiver accepts.
  • Each spend writes the spender party into the transfer metadata under the key openzeppelin.com/spender.
  • Each spend needs explicit disclosure of the owner's funding holdings. A partial spend returns change at a new holding contract ID, so a disclosure cannot be used twice.
  • A spend archives the allowance and creates the remaining budget at a new contract ID. Wallets must track the new ID.

The owner's account parties can revoke an allowance in three ways. The admin cannot revoke it.

  • TokenAllowance_Revoke.
  • TokenAllowance_SetRemaining with zero. A positive value creates the allowance again at a new contract ID.
  • TokenRules_ApproveAllowance with amount zero and the current contract ID, as with ERC-20 approve(spender, 0).

V1 Compatibility

The V2 interfaces are separate from V1. They share only splice-api-token-metadata-v1, so V1 tooling cannot see this token. To support V1, add the V1 interface instances to the same templates. The V1 DARs are in dars/vendor/, and the vendored splice-token-standard-utils DAR has helpers for this.

Build and Consume

From the root of canton-contracts:

DAML_PACKAGE=experiments/token/tokenCIP112-v1 dpm build

In your project, add the token DAR and the vendored interface DARs as data-dependencies:

data-dependencies:
  - ../canton-contracts/experiments/token/tokenCIP112-v1/.daml/dist/openzeppelin-tokenCIP112-v1-0.1.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-metadata-v1-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-holding-v1-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-holding-v2-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-transfer-events-v2-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-allocation-v1-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-allocation-v2-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-transfer-instruction-v1-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-transfer-instruction-v2-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-allocation-instruction-v1-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-allocation-instruction-v2-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-allocation-request-v1-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-api-token-allocation-request-v2-1.0.0.dar
  - ../canton-contracts/dars/vendor/splice-token-standard-utils-2.0.0.dar
build-options:
  - --target=2.1

The vendored DARs are built with SDK 3.5.1 and used from the 3.4.11 baseline. This works because both target Daml-LF 2.1, so --target=2.1 is required. dars/manifest.yaml records the source commit and digest of each vendored DAR.

Testing

  • Unit tests. dpm test runs the Daml Script suites on an in-memory ledger. CI requires full template and choice coverage.
  • Sandbox gate. scripts/check-sandbox.sh runs token creation, transfer, allowance, query, and burn scripts against a static-time Canton sandbox over the Ledger API. It needs dpm, Java 21 or newer, lsof, and a free Ledger API port (6865 by default, or set OZ_LEDGER_PORT). CI does not run this gate.