CIP-0127: On-Chain Enforcement of FA and SV Locking (Implementation of CIP-0116 and CIP-0105)
Abstract
This CIP serves to align the stakeholders of CIP-0105 (SV Locking) and CIP-0116 (FA Locking) on the implementation of on-chain enforcement of SV and FA locking. It is motivated by the fact that the concrete mechanisms chosen to implement locking have a material impact on the operability of SVs, FAs, and staking apps.
The proposed implementation closely follows the high-level guidance laid out by these two CIPs except for the following changes:
- Add a grace period for underlocked FA rights during which they are only temporarily suspended. The grace period defaults to seven days and can be changed by SV voting. It serves to reduce the trust that FA operators need to have in their lock owners; and thus increases the expected total amount locked by FA locks.
- Clarify that the termination of the SV lock-up requirement stops the enforcement of the lock-up, and SV lock funds are released via vesting to allow for a clearer and more orderly operational transition to the next phase of governance.
- Drop the seven day grace period for the enforcement of temporary loss of SV weight because that grace period is not economically motivated, but was added to lower the operational overhead of manual enforcement via SV votes.
- Require locks to lock a minimum amount to avoid “dust” locks whose management overhead surpasses their value. The minimum lock amount defaults to 10k CC and can be changed by SV voting.
- Support configurable controllers for unlocking, substitution and withdrawing vested funds to simplify building staking apps.
The CIP further clarifies the technical integration between wallets and locks; and it proposes incremental delivery and migration paths, thereby creating clarity for the work required from the stakeholders to land on-chain enforcement of SV and FA locks on MainNet.
Specification
This specification explains the concrete mechanisms chosen to implement on-chain enforcement of CIP-0105 (SV Locking) and CIP-0116 (FA Locking). It is split into three sections: high-level specification, incremental delivery plan, and technical specification.
The high-level specification is aimed at business stakeholders that want to understand the mechanisms chosen to implement locking; e.g., because they want to figure out the business workflows for a staking app. It also serves as the functional specification for the implementation.
The incremental delivery plan specifies how the full specification is broken down into milestones that can be delivered incrementally, enabling the early delivery of key features. It furthermore explains the expected migration paths to on-chain enforcement for both FA and SV locking.
The technical specification builds on the high-level specification. It clarifies technical aspects where required to clarify the interaction between different systems or applications.
High-Level Specification
Terminology
Apps:
- App provider: the organization operating an application
- App provider party: the party representing an application on-ledger
- Featured app (FA): an application whose app provider party was granted featured application status
SVs:
- SV rights owner: an entity with the right to earn SV rewards
- SV rewards beneficiary: the party that is allowed to mint the SV rewards for (part of) the SV weight of an SV rights owner
Governance locks:
- (Governance) lock: a CC holding locked for the purpose of fulfilling the locking requirements mandated by the governance rules specified for SVs in CIP-0105 and for FAs in CIP-0116.
- SV lock: a governance lock created to satisfy SV locking requirements
- FA lock: a governance lock created to satisfy FA locking requirements
- Provisional FA lock: a governance lock created to apply for an FA right
- lock owner: the party that owns the funds locked in a governance lock
- lock subject: the name of the SV rights owner or FA party whose locking requirements the governance lock contributes to.
- vesting lock: a representation of a CC holding whose amount was locked as a governance lock, but is now unlocking in a vested fashion and no longer counts toward the fulfilment of locking requirements.
Wallet Integration
Two wallet integration options are supported:
- Full feature integration: all workflows described in this CIP will be exposed via Daml interface APIs for staking apps and wallets to integrate with. Wallets are expected to build dedicated UIs for these workflows to build a full feature integration.
- Compatibility mode: a compatibility mode with a reduced feature set allows using any wallet that supports CIP-56 Token Standard V1 (TSv1) two-step transfers to create locks, unlock them, and withdraw vested funds in daily tranches.
The compatibility mode enables the initial deployment of SV and FA locks without requiring wallets to support the full-feature workflows. It will remain available alongside the full feature integration.
We expect the wallet ecosystem to migrate over time to full feature integrations with dedicated UIs for interacting with governance locks. We further expect the Daml inteface APIs to be used by all apps (e.g., staking apps) that want to interact with governance locks. See Wallet Integration Details for details on the wallet integration.
Lock Lifecycle
The following sections describe the lock lifecycle. See the final section, titled Compatibility Mode, for details on how the lock lifecycle works when using the compatibility mode based on TSv1 two-step transfers.
Creation
All locks are created by the lock owner. They must specify the lock type (FA or SV), the lock subject, and the lock amount. They may also specify custom controllers for the unlock, withdraw and substitution actions explained below.
Controllers on Lock Actions
By default the lock owner controls all actions on the lock.
To change this default, the lock owner can specify custom controllers for each lock action when creating the lock. The custom controllers are specified as a set of sets of parties. If all parties in any one of these sets authorize the action, then it happens. All controllers are made observers of the lock contract, so they can monitor its state.
The controllers can provide their authorization for an action individually one after the other in their own Daml transaction; or jointly in a single transaction. The former is useful when the controllers are each using their own wallet to authorize an action. They do so by executing the action they want to confirm, which only happens once a sufficient set of controllers have authorized it. Joint authorization in a single transaction is useful when their authorization is managed via a third-party app.
Example: Creating an FA Lock
The following FA lock is an example of a lock that a staking app S might create:
- FA lock with
- lock owner:
A - lock subject:
X - amount: 5M CC
- substitution controllers:
{S, X} | {S, A} | {A, X} - unlock controllers:
{S, X} | {S, A} | {A, X} - vesting controllers:
{S} | {A}
- lock owner:
The lock owner A is the party that owns the 5M CC that are locked for the featured app party X.
The substitution controllers and the unlock controllers are the same. They are chosen such that any two out of the three parties {A, X, S} can take the action on their own. This reflects that the staking app acts as a trusted third-party between X and A, but A and X also reserve the right to act independently of S.
The vesting controllers control the disbursal of vesting funds, which can happen via withdrawals, substitutions, or transfers.
In this example, they are chosen such that S can automate the withdrawal or substitution of vested funds on behalf of A without an extra delegation contract, but A can also drive substitutions and withdrawals themselves.
Automatic Lock Merging
Locks are identifed by their key, which consists of the lock owner, the lock subject, the lock type, the vesting state, and the custom controllers. When a new lock is created with the same key as an existing lock its amount is automatically merged into the existing one. This can for example be used to topup an existing lock.
Lock merging is performed on a best effort basis. Concurrent lock creations may result in separate locks with the same key being created. Furthermore the automatic merging will merge at most 20 locks with the same key to avoid overly large transactions.
Requesting the creation of a lock with amount 0.0 can be used to trigger the automatic merging of existing locks with the same key.
Repeated merges can be used to eventually merge all existing locks with the same key, even if there are more than 20.
Example: Automatic Lock Merging
Suppose A creates another FA lock like the one from the previous example, but with a lock amount of 1k CC.
The new lock is automatically merged with the existing one, resulting in this one lock:
- FA lock with
- lock owner:
A - lock subject:
X - amount: 5,001,000 CC
- substitution controllers:
{S, X} | {S, A} | {A, X} - unlock controllers:
{S, X} | {S, A} | {A, X} - vesting controllers:
{S} | {A}
- lock owner:
Suppose A then additionally creates the following FA lock:
- FA lock with
- lock owner:
A - lock subject:
X - amount: 1M CC
- substitution controllers:
{S, A} - unlock controllers:
{S, A} - vesting controllers:
{S} | {A}
- lock owner:
This lock is not merged with the previous one because its has different substitution and unlock controllers.
Unlocking
The parties specified as unlock controllers on a governance lock can request unlocking of (part of) a lock. Once enough unlock controllers have requested the unlocking, the lock is converted into a vesting lock with the vesting schedule parameters taken from the SV and FA lock vesting parameters managed via SV governance.
By default the whole locked amount is unlocked at once. A specific amount can be unlocked by passing the amount as metadata.
Provisional FA Locks
CIP-0116 requires featured app providers to prove that they have sufficient funds before applying for a featured app right. They can do so by creating a provisional FA lock that works like a normal FA lock except that on unlocking no vesting applies and the funds are immediately returned to the owner.
Withdrawing Vested Funds
The vesting controllers of a vesting lock can request to withdraw the vested amount from a vesting lock and return it to the lock owner as liquid CC.
The release of vested amounts works in a pull-based fashion: the vesting controllers specify the time up to which vested funds should be computed and released. Provided this time is (a) in the past, (b) later than the creation of the vesting lock, and (c) later than the last time of withdrawal, the funds are released to the lock owner. If no locked funds remain, then the lock is archived.
Automated Return of Vested Funds
By default lock owners are expected to manage the withdrawal of vested funds on their own, potentially with the help of the vesting controllers.
Independently of the owners and vesting controllers, all SVs may run automation to clean up any fully vested vesting lock and return the vested funds as liquid CC to their owner. The SVs may use this for example to reduce the storage consumption of the SV nodes.
Example: Unlocking Part of an FA Lock
Let us continue the prior example of the following FA lock:
- FA lock with
- lock owner:
A - lock subject:
X - amount: 5M CC
- substitution controllers:
{S, X} | {S, A} | {A, X} - unlock controllers:
{S, X} | {S, A} | {A, X} - vesting controllers:
{S} | {A}
- lock owner:
Assume that A requests the unlocking of 1M CC from this lock via the staking app S. We would expect that the staking app checks whether that unlock is compliant with the staking deal. Assume it is and S makes the following unlock request at time t0:
- Request unlock:
- actors:
{S, A} - target lock:
cid1 - amount: 1M CC
- actors:
The result of this request is:
- FA lock with
- lock owner:
A - lock subject:
X - amount: 4M CC
- substitution controllers:
{S, X} | {S, A} | {A, X} - unlock controllers:
{S, X} | {S, A} | {A, X} - vesting controllers:
{S} | {A}
- lock owner:
- Vesting lock with
- lock owner:
A - amount vesting: 1M CC
- vesting start:
t0 - vesting end:
t0 + 60 days - vesting controllers:
{S} | {A}
- lock owner:
Example: Withdrawing Vested Funds
Continuing the above example, assume that S automates the withdrawal of vested funds in quarter-period increments, i.e., every 15 days. Thus after 15 days, S instructs the withdrawal of vested funds which results in:
- payout of 1'000'000 / (60 / 15) = 250'000 CC
- updated Vesting Lock with
- lock owner:
A - amount vesting: 750'000 CC
- vesting start:
t0 + 15 days - vesting end:
t0 + 60 days - vesting controllers:
{S} | {A}
- lock owner:
Note that the amount vesting and the vesting start are changed to represent the remaining vesting amount and its remaining vesting period. This representation works well with substitution, as can be seen in this example.
Substitution
Funds owners can create a proposal to use their funds to substitute some (or all) of the locked amount of an existing lock. Substitution works for all types of locks independently of whether they are vesting or not. Substitutions of vesting locks are approved by the vesting controllers, while substitutions of non-vesting locks are approved by the substitution controllers. The new lock to be created is specified as part of the substitution. While it must have the same type as the existing lock, it can have different controllers.
A substitution generally results in two locks of the same type with the same lock subject whose total amount is equal to the amount of the existing lock. The substituted funds in the existing lock are released as liquid CC to the lock owner of the existing lock. Locks whose locked amount would be zero are not created.
In the spirit of maximizing operational flexibility, a special provision is made for substitutions proposed by the owner of the targeted existing lock. Such a proposal does not lock any funds. Instead the funding of the resulting new locks is provided by splitting the funds of the existing lock. This allows lock owners to update the lock controllers without requiring any extra liquidity. As for normal substitutions, the substitution controllers on the existing lock must approve the substitution for it to succeed.
Minimum lock amounts are enforced on all locks resulting from partial substitutions. Lock owners are encouraged to lock amounts that are multiples of the minimum lock amount to avoid failed partial substitutions.
Substitution proposals expire after 90 days to prevent indefinite pending substitutions.
Substitution Target Resolution
Substitution proposals specify the target lock by value. Concretely they specify type, owner, subject, controllers, and vesting state of their target lock. The amount is intentionally not included to avoid substitutions that cannot be accepted because the amount changed due to concurrent partial unlocks or partial substitutions.
The implementation uses contract keys to resolve this target to all locks that match the specification. If there are multiple locks, then it merges up to 20 of them before applying the substitution to reduce the chance of failed substitutions due to insufficient amounts in individual locks.
Example: Substitution of Locked Funds
Assume given:
- App provider parties:
X,Y - Funds owner parties:
A,B - Staking app party:
S - FA lock with
- lock owner:
A - lock subject:
X - amount: 5M CC
- substitution controllers:
{S, X}
- lock owner:
Assume that B creates the following substitution proposal:
- Substitution proposal with
- target
- FA lock with
- lock owner:
A - lock subject:
X - amount: 5M CC
- substitution controllers:
{S, X}
- lock owner:
- FA lock with
- new lock
- lock owner:
B - lock subject:
X - amount: 1M CC
- substitution controllers:
{S, B} | {S, X}
- lock owner:
- reserved holdings: 1M CC
- target
Note that the creation requires B to provide 1M of CC holdings to back the proposal.
Assume both S and X approve the substitution proposal, which results in:
- archival of the substitution proposal
- payout of 1M CC to
A - updated existing FA lock with
- lock owner:
A - lock subject:
X - amount: 4M CC
- substitution controllers:
{S, X}
- lock owner:
- new FA lock with
- lock owner:
B - lock subject:
X - amount: 1M CC
- substitution controllers:
{S, B} | {S, X}
- lock owner:
Note that the substitution proposal does not specify the type of the new lock. That is copied from the target lock. Thus the new lock is of the same type by construction.
The substitution proposal does however specify the substitution controllers for the new lock, which are different between the locks in this example. On the existing lock, there is no option for the lock owner A to approve a substitution. However on the new lock, the lock owner B can approve a substitution provided the staking app S agrees. This is important as the different owners do not necessarily have the same requirements with respect to who controls their lock substitutions.
Example: Changing the lock controllers using a substitution
Assume that the staking app S authorizes substitutions on their own using just {S} as the substitution controllers, and there exists the following lock:
- FA lock with
- lock owner:
A - lock subject:
X - amount: 1M CC
- substitution controllers:
{S}
- lock owner:
The app now adds a feature to hand back control over a lock to the lock owner. The app can execute this change of control by getting A to propose this substitution:
- Substitution proposal with
- substitution target:
- FA lock with
- lock owner:
A - lock subject:
X - substitution controllers:
{S}
- lock owner:
- FA lock with
- new lock
- lock owner:
A - lock subject:
X - amount: 1M CC
- substitution controllers:
{A}
- lock owner:
- reserved holdings: 0M CC
- substitution target:
Note that no holdings are reserved, as the lock owner does not change. Once S accepts the substitution, the result is:
- updated FA lock with
- lock owner:
A - lock subject:
X - amount: 1M CC
- substitution controllers:
{A}
- lock owner:
Example: Substitution of a Vesting Lock
The earlier example of withdrawing from a vesting lock resulted in the following vesting lock:
- Vesting Lock with
- lock owner:
A - amount vesting: 750'000 CC
- vesting start:
t0 + 15 days - vesting end:
t0 + 60 days - vesting controllers:
{S} | {A}
- lock owner:
Assume B agreed to replace 250k CC of these vesting funds. They would do so by creating the following proposal:
- Substitution proposal with
- target
- Vesting Lock with
- lock owner:
A - vesting start:
t0 + 15 days - vesting end:
t0 + 60 days - vesting controllers:
{S} | {A}
- lock owner:
- Vesting Lock with
- new lock
- lock owner:
B - amount: 250k CC
- vesting controllers:
{S, B}
- lock owner:
- reserved holdings: 250k CC
- target
Note that specifying the vesting parameters of the target matters, as they determine the vesting parameters of the substituted funds.
Assume further that S and A jointly accept the proposal, which results in:
- payout of 250k CC to
A - archival of the proposal
- Vesting Lock with
- lock owner:
A - amount vesting: 500'000 CC
- vesting start:
t0 + 15 days - vesting end:
t0 + 60 days - vesting controllers:
{S} | {A}
- lock owner:
- Vesting Lock with
- lock owner:
B - amount vesting: 250'000 CC
- vesting start:
t0 + 15 days - vesting end:
t0 + 60 days - vesting controllers:
{S, B}
- lock owner:
Note that not only the amount changed, but also the vesting controllers as specified in the substitution proposal.
Transfers
Lock transfers allow changing the ownership of a lock's funds without changing the lock type, subject and vesting state. This is useful for example to change the funds custodian without going through vesting or prefunding a substitution.
Transfers can be seen as special form of substitutions. They work similarly to substitutions, but are initiated by the owner of the lock whose funds are being transferred and require no prefunding.
A transfer proposal is created by the current lock owner. It specifies the owner and amount of the new lock to create as a result of the transfer. It identifies and resolves the target lock by value analogous to substitutions.
To complete the transfer, the proposal must be accepted by the new owner and the substitution controllers of the target lock (i.e., the vesting controllers for a vesting lock). The new owner may also specify the custom controllers for the new lock as part of accepting it. If they do not, then the new lock will be owner-controlled.
Minimum lock amounts are enforced on all locks resulting from partial transfers. Lock owners are encouraged to lock amounts that are multiples of the minimum lock amount to avoid failed partial transfers.
Transfer proposals expire after 90 days to prevent indefinite pending transfers.
Example: Transfer of an FA Lock
Assume the following FA lock exists:
- FA lock with
- lock owner:
A - lock subject:
X - amount: 5M CC
- substitution controllers:
{S, A}
- lock owner:
A creates the following transfer proposal to transfer 2M CC to B:
- Transfer proposal with
- target
- FA Lock with
- lock owner:
A - lock subject:
X - substitution controllers:
{S, A}
- lock owner:
- FA Lock with
- new lock
- lock owner:
B - amount: 2M CC
- lock owner:
- target
The proposal does not reserve funds.
Assume that B accepts the transfer proposal and specifies substitution controllers {S, B} for the new lock, which results in:
- Transfer proposal with
- target
- FA Lock with
- lock owner:
A - lock subject:
X - substitution controllers:
{S, A}
- lock owner:
- FA Lock with
- new lock
- lock owner:
B - amount: 2M CC
- substitution controllers:
{S, B}
- lock owner:
- approved by:
{B}
- target
Once the transfer is also approved by substitution controllers of the existing lock S and A, the transfer completes and results in:
- FA lock with
- lock owner:
A - lock subject:
X - amount: 3M CC
- substitution controllers:
{S, A}
- lock owner:
- FA lock with
- lock owner:
B - lock subject:
X - amount: 2M CC
- substitution controllers:
{S, B}
- lock owner:
Minting SV Rewards into Locks
When creating a lock, SV rewards can be minted directly into the lock, which is useful in tax regimes that treat unvested and vested rewards differently. When doing so, the lock owner can specify the percentage of rewards that should be added to the locked funds. Thereby making it easy for SVs to mint and lock the percentage of their rewards matching their desired tier.
Example: Mint Locked SV Rewards
Assume that an SV ExampleSV uses party A to lock the required funds for their SV right, and they target to earn 100% of their SV rights. At the start of the weight enforcement period this requires them to lock 70% of their SV rewards. They set up their SV right such that their SV reward coupons all specify party A as the beneficiary.
Thus every round they have:
- SV Lock with:
- lock owner:
A - lock subject:
ExampleSV - amount:
<current-amount>CC
- lock owner:
- SV reward coupon with:
- contract-id:
cid2 - round:
<r> - beneficiary:
A - weight:
<example-weight>
- contract-id:
The minting automation for party A can issue the following lock creation request to mint the SV reward coupon and lock 70% of the minted CC:
- Create SV lock with:
- lock owner:
A - lock subject:
ExampleSV - SV reward locking percentage:
70% - SV reward coupons:
[cid2]
- lock owner:
Because created locks are auto-merged, the existing lock will be increased by 70% of the minted CC. The result is:
- SV Lock with:
- lock owner:
A - lock subject:
ExampleSV - amount:
<current-amount> + <round r issuance per SV weight> * <example-weight> * 0.7CC
- lock owner:
- payout of liquid CC of
<round r issuance per SV weight> * <example-weight> * 0.3
Minimum Lock Amount
Similar to lot sizes in TradFi, locks must lock a minimum amount configured by SV voting (default 10k CC). This minimum amount serves to avoid users creating “dust locks” whose management overhead exceeds their value.
The minimum amount restriction is enforced on all actions that create additional locks; e.g., when creating a new lock or when doing a partial unlock or a partial substitution both of the resulting locks must be larger than the minimum amount. Actions that archive at least one existing lock and result in a single new lock are allowed to produce locks with an amount below the minimum. For example, it is always possible to substitute or unlock the whole lock amount or to topup existing locks via auto-merging, even if the SVs voted to increase the minimum lock amount after the locks were created.
Note that new SVs will need to lock the minimum lock amount once they are onboarded to avoid losing their SV weight as shown in this example.
Compatibility Mode
Funds owners whose wallets do not provide a full-feature integration can use TSv1 two-step transfers to manage owner-controlled locks.
Lock Display in Wallets
Locks are displayed in wallets as pending TSv1 transfers with
- sender:
<funds-owner-party-id> - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - memo tag:
cip-127/memo:<lock-parameters>
where the lock parameters shown as key=value pairs separated by &. Governance locks will have the following key-value pairs:
lock-kind=<sv-lock|fa-lock|provisional-fa-lock>lock-subject=<sv-name|fa-party-id>lock-status=locked
Vesting locks will have the following key-value pairs:
lock-kind=<sv-lock|fa-lock|provisional-fa-lock>lock-subject=<sv-name|fa-party-id>lock-status=vestinglock-vesting-duration-micros=<vesting duration in microseconds>lock-vesting-end-time-micros=<vesting end time in microseconds since unix epoch>lock-vesting-info="vesting for <decimal> days until <date>"
For example, assume there is a vesting FA lock created at the start of the Unix epoch 1970-01-01T00:00:00Z:
- Vesting FA lock with
- lock owner:
A - lock subject:
X - amount vesting: 1M CC
- vesting duration:
60 days=5,184,000seconds - vesting end:
0 + 60 days=60 × 24 × 60 × 60=5,184,000seconds after Unix epoch
- lock owner:
This lock would be displayed in the wallet as a pending TSv1 transfer with:
- sender:
A - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount: 1M CC
- memo:
cip-127/memo:lock-kind=fa-lock&lock-subject=X&lock-status=vesting&lock-vesting-duration-micros=5184000000000&lock-vesting-end-time-micros=5184000000000&lock-vesting-info="vesting for 60.0 days until 1970-03-02T00:00:00Z"
The reason for representing the vesting durations and end times in microseconds is to enable precise
round-tripping of that information when specifying the target of substitutions and transfers.
The lock-vesting-info field provides a human-readable summary of the vesting schedule.
Lock Actions
Actions on locks are performed by initiating a TSv1 transfer to the special cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd party with a memo tag that specifies the request and its parameters.
The supported actions are: creating locks, starting vesting, withdrawing vested funds, substituting locks, and transferring locks.
We explain them in the following sections.
Creating Locks
- sender:
<funds-owner-party-id> - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount:
<amount-to-lock> - memo tag:
cip-127/memo:request=create-lock&lock-kind=<sv-lock|fa-lock|provisional-fa-lock>&lock-subject=<sv-name|fa-party-id>
The SV rights owner names correspond to the names that are currently specified in approved-sv-id-values.yaml. Only minimal fat-finger error protection is provided: they only check that (a) the memo tag field starts with lock-subject=, (b) the parsed SV rights owner names consist of alphanumeric characters and hyphens (-), and (c) the FA parties are registered parties on the global synchronizer. It is the responsibility of the funds owner to specify the right values.
Example: Party A can create a 5M CC FA lock for app provider party X by initiating the following transfer:
- sender:
A - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount: 5M CC
- memo tag:
cip-127/memo:request=create-lock&lock-kind=fa-lock&lock-subject=X
Unlocking Locks
- sender:
<funds-owner-party-id> - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount:
<ignored> - memo tag:
cip-127/memo:request=unlock-and-start-vesting&unlock-amount=<amount-to-unlock>&vesting-start-time=<vesting start time in ISO 8601 format>&lock-kind=<sv-lock|fa-lock|provisional-fa-lock>&lock-subject=<sv-name|fa-party-id>
Note that the amount field in the transfer instruction is not used as the
specification of the amount to unlock, as that would require wallets to provide
input holdings over the whole amount to unlock.
Ideally, wallets allow specifying 0.0 as the input amount and
do not fetch any input holdings in that case.
Specifying any other amount is also possible, but it will be ignored by the lock manager
and the provided holdings will be returned unchanged.
The transfer must be submitted to the network before the vesting start time, as otherwise the vesting schedule could be circumvented by backdating the vesting start time. The transfer is rejected if the vesting start is more than 24h in the future to avoid fat finger mistakes.
Unlocking provisional locks does not require to specify the vesting start time, as provisional FA locks do not go through a vesting period.
Example: Assume party A has the following FA lock:
- FA lock with
- lock owner:
A - lock subject:
X - amount: 5M CC
- lock owner:
A can unlock 1M CC from this lock, starting vesting at
2030-01-01T00:00:00Z, by initiating the following transfer:
- sender:
A - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount: 0.0 CC
- memo tag:
cip-127/memo:request=unlock-and-start-vesting&unlock-amount=1000000&vesting-start-time=2030-01-01T00:00:00Z&lock-kind=fa-lock&lock-subject=X
The transfer amount is ignored; unlock-amount specifies how much to unlock. The vesting start
time is 2030-01-01T00:00:00Z expressed in ISO 8601 format using the UTC timezone.
Assuming the FA vesting duration is 60 days, the result is a 1M CC vesting lock displayed in the
wallet as the following pending transfer instruction:
- sender:
A - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount: 1M CC
- memo tag:
cip-127/memo:lock-kind=fa-lock&lock-subject=X&lock-status=vesting&lock-vesting-duration-micros=5184000000000&lock-vesting-end-time-micros=1898640000000000&lock-vesting-info="vesting for 60.0 days until 2030-03-02T00:00:00Z"
Withdrawing from Vesting Locks
- sender:
<funds-owner-party-id> - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount:
<ignored> - memo tag:
cip-127/memo:request=withdraw-vested-funds&vested-until-time=<withdrawal time in ISO 8601 format>&lock-kind=<sv-lock|fa-lock|provisional-fa-lock>&lock-subject=<sv-name|fa-party-id>&lock-status=vesting&lock-vesting-duration-micros=<vesting duration in microseconds>&lock-vesting-end-time-micros=<vesting end time in microseconds since unix epoch>
As with unlocking, the transfer amount is ignored; wallets should use 0.0 when possible.
The vested-until-time specifies the point as of which vested funds should be computed and withdrawn.
It must be in the past.
The remaining fields identify the vesting lock from which the funds are being withdrawn.
They can be copied from the original lock's memo as displayed in the wallet.
Example: Continuing from the unlock example above, after 2030-01-16T00:00:00Z party A can
withdraw the 250,000 CC vested in the first 15 days by submitting:
- sender:
A - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount: 0.0 CC
- memo tag:
cip-127/memo:request=withdraw-vested-funds&vested-until-time=2030-01-16T00:00:00Z&lock-kind=fa-lock&lock-subject=X&lock-status=vesting&lock-vesting-duration-micros=5184000000000&lock-vesting-end-time-micros=1898640000000000
The result is a payout of 250,000 CC to A and the following updated vesting lock, displayed in the wallet as a pending transfer instruction:
- sender:
A - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount: 750,000 CC
- memo tag:
cip-127/memo:lock-kind=fa-lock&lock-subject=X&lock-status=vesting&lock-vesting-duration-micros=3888000000000&lock-vesting-end-time-micros=1898640000000000&lock-vesting-info="vesting for 45.0 days until 2030-03-02T00:00:00Z"
Substituting Locks
Substitutions are initiated by the owner of the substituted funds using a transfer of the following form:
- sender:
<new-lock-owner-party-id> - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount:
<amount-to-substitute> - memo tag:
cip-127/memo:request=substitute-lock&target-lock-owner=<target-lock-owner-party-id>&lock-kind=<sv-lock|fa-lock|provisional-fa-lock>&lock-subject=<sv-name|fa-party-id>&lock-status=<locked|vesting>- for vesting locks, also include
lock-vesting-duration-micros=<vesting duration in microseconds>&lock-vesting-end-time-micros=<vesting end time in microseconds since unix epoch>
The result is a pending transfer instruction visible to both <new-lock-owner-party-id> and <target-lock-owner-party-id> with the following details:
- sender:
<new-lock-owner-party-id> - receiver:
<target-lock-owner-party-id> - amount:
<amount-to-substitute> - memo tag:
cip-127/memo:request=accept-to-substitute-lock&lock-kind=<sv-lock|fa-lock|provisional-fa-lock>&lock-subject=<sv-name|fa-party-id>&lock-status=<locked|vesting>- for vesting locks, also include
lock-vesting-duration-micros=<vesting duration in microseconds>&lock-vesting-end-time-micros=<vesting end time in microseconds since unix epoch>
The target lock owner can accept or reject the substitution by accepting or rejecting the pending transfer instruction using their wallet. The new lock owner can withdraw the substitution by withdrawing the pending transfer instruction using their wallet.
Example: Assume party A owns the following FA lock:
- FA lock with
- lock owner:
A - lock subject:
X - amount: 5M CC
- lock owner:
Party B can propose substituting 1M CC of this lock by initiating the transfer:
- sender:
B - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount: 1M CC
- memo tag:
cip-127/memo:request=substitute-lock&target-lock-owner=A&lock-kind=fa-lock&lock-subject=X&lock-status=locked
This creates a pending transfer instruction for A to accept:
- sender:
B - receiver:
A - amount: 1M CC
- memo tag:
cip-127/memo:request=accept-to-substitute-lock&lock-kind=fa-lock&lock-subject=X&lock-status=locked
If A accepts this transfer instruction, then 1M CC is paid out to A, the existing FA lock is reduced to 4M CC, and a new owner-controlled FA lock for B is created with subject X and amount 1M CC.
B's wallet will display the new lock as this pending transfer instruction:
- sender:
B - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount: 1M CC
- memo tag:
cip-127/memo:lock-kind=fa-lock&lock-subject=X&lock-status=locked
Transferring Locks
Transfers are initiated by the current owner of the target lock using a transfer of the following form:
- sender:
<current-lock-owner-party-id> - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount:
<ignored> - memo tag:
cip-127/memo:request=transfer-lock&new-lock-owner=<new-lock-owner-party-id>&transfer-amount=<amount-to-transfer>&lock-kind=<sv-lock|fa-lock|provisional-fa-lock>&lock-subject=<sv-name|fa-party-id>&lock-status=<locked|vesting>- for vesting locks, also include
lock-vesting-duration-micros=<vesting duration in microseconds>&lock-vesting-end-time-micros=<vesting end time in microseconds since unix epoch>
The result is a pending transfer instruction visible to both <current-lock-owner-party-id> and <new-lock-owner-party-id> with the following details:
- sender:
<current-lock-owner-party-id> - receiver:
<new-lock-owner-party-id> - amount:
<amount-to-transfer> - memo tag:
cip-127/memo:request=accept-to-transfer-lock&lock-kind=<sv-lock|fa-lock|provisional-fa-lock>&lock-subject=<sv-name|fa-party-id>&lock-status=<locked|vesting>- for vesting locks, also include
lock-vesting-duration-micros=<vesting duration in microseconds>&lock-vesting-end-time-micros=<vesting end time in microseconds since unix epoch>
The new lock owner can accept or reject the proposal by accepting or rejecting the pending transfer instruction using their wallet. The current lock owner can withdraw the proposal by withdrawing the pending transfer instruction.
Example: Assume party A owns the following FA lock:
- FA lock with
- lock owner:
A - lock subject:
X - amount: 5M CC
- lock owner:
Party A can propose transferring 2M CC of this lock to party B by initiating:
- sender:
A - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount: 0.0 CC
- memo tag:
cip-127/memo:request=transfer-lock&new-lock-owner=B&transfer-amount=2000000&lock-kind=fa-lock&lock-subject=X&lock-status=locked
This creates a pending transfer instruction for B to accept:
- sender:
A - receiver:
B - amount: 2M CC
- memo tag:
cip-127/memo:request=accept-to-transfer-lock&lock-kind=fa-lock&lock-subject=X&lock-status=locked
If B accepts this transfer instruction, the existing FA lock remains with A and is reduced to 3M CC, and a new owner-controlled FA lock for B is created with subject X and amount 2M CC. B's wallet will display the new lock as this pending transfer instruction:
- sender:
B - receiver:
cip-127_lock-manager::1220000000000000000000000000000000000000000000000000000000000000abcd - amount: 2M CC
- memo tag:
cip-127/memo:lock-kind=fa-lock&lock-subject=X&lock-status=locked
Limitations
The limitations of the compatibility mode are the following:
- no support for managing locks with custom controllers
- no support for locked SV reward minting
- the locks show as long-lived transfer offers in the wallet UI
Automatic Enforcement of FA Underlocking
We propose to implement automatic enforcement of FA underlocking using the following three mechanisms, which we explain below:
- Configuring the minimum lock threshold for an FA right
- Enforcing underlocks on-chain
- Detecting underlocks and triggering on-chain enforcement using SV automation
We propose to configure minimum lock thresholds using a combination of a network-wide threshold and an FA specific override. Both are set using SV voting. To minimize the number of votes required to set the right thresholds, we propose to default the network-wide threshold to 5M CC and use votes to override the thresholds of featured asset issuers to the required 25M CC.
To increase operational flexibility, we propose to enforce underlocks with a 7 day grace period (configurable using SV voting) that works as follows:
- Immediate suspension: while an FA is underlocked, it is suspended, which means that it no longer creates featured app markers nor does it participate in traffic-based app rewards for rounds opened while it is suspended.
- Permanent loss: if the underlock remains active for more than the 7 day grace period, then the FA status is permanently lost.
Underlocks are detected by SV automation that regularly computes the totals of all non-provisional FA locks, and compares them against the required thresholds. If an underlock is detected, then the automation triggers the on-chain action that implements the enforcement described above.
Example: Suspension and Recovery
Assume asset provider X has the following FA right and FA lock:
- FA Right:
- provider:
X - minimum lock amount override: 25M CC
- provider:
- FA Lock:
- lock owner:
X - lock subject:
X - amount: 25M CC
- unlock controllers:
{X}
- lock owner:
For some reason A decides to unlock 5M CC at t0, which results in:
- Vesting lock:
- lock owner:
X - amount: 5M CC
- vesting start:
t0 - vesting end:
t0 + 60 days
- lock owner:
- FA Lock:
- lock owner:
X - lock subject:
X - amount: 20M CC
- unlock controllers:
{X}
- lock owner:
A few seconds later at t1 > t0, the SV node automation detects the underlock and triggers the on-chain enforcement, which results in:
- updated FA Right:
- provider:
X - minimum lock amount override: 25M CC
- underlocked:
True - underlock becomes permanent at:
t1 + 7 days
- provider:
The effect of this change is that transactions that attempt to use this FA right to create featured app markers succeed, but do not create any app markers; and the computations recording traffic-based app activity records no longer consider X a featured app for rounds that open after t1.
A day later, app provider X asks funds owner A to lock 5M CC in exchange for some interest, which results in the following additional lock:
- FA Lock:
- lock owner:
A - lock subject:
X - amount: 5M CC
- unlock controllers:
{A}
- lock owner:
A few seconds later, the SV node automation detects the underlock recovery and reports this on-chain, which results in:
- updated FA Right:
- provider:
X - underlocked:
False - minimum lock amount override: 25M CC
- provider:
Thus the FA right again creates featured app markers and is respected in the computations for traffic-based app activity records for rounds that open after this time.
Example: Permanent Loss
Assume that in the above example X does not manage to get somebody to lock the 5M CC before t1 + 7 days. In that case, SV automation will trigger the archival of the FA right, and its loss becomes permanent.
No automatic unlocking of funds happens. However X can unlock their remaining 20M CC, which will then result in a 60 day Vesting Lock.
Automated SV Reward Locking
We propose to extend the reward minting automation of validator nodes to mint a percentage of SV rewards directly in locked form. Concretely, we expect validator nodes to accept a configuration as shown in the following example:
canton.validator-apps.validator_backend {
reward-minting-config-by-party = {
"alice::1220abc...def" = {
sv-reward-minting-config-by-sv-name = {
"ExampleSV1" = {
# Mint 70% of SV rewards for "ExampleSV" in a lock for them
mint-locked-percentage = 0.7
}
"ExampleSV2" = {
# Do not mint coupons for "ExampleSV2", as their minting is
# handled by automation outside the validator node.
skip-minting = true
}
}
}
}
The second config option shown for ExampleSV2 serves to handle cases where an SV uses a third-party automation to mint their coupons. It avoids having validator node automation compete with that third-party automation.
For the minting automation to work for ExampleSV1, it must hold that:
ExampleSV1configuresalice::1220abc...defas an SV reward beneficiary on the SV node that hostsExampleSV1.- There exists an SV lock with lock owner
alice::1220abc...defand lock subjectExampleSV1. - The
alice::1220abc...defparty is hosted in at least observer mode on the validator node running the automation. - In case
alice::1220abc...defis an external party, their owner has set up a minting delegation with a party hosted on the validator node running the automation.
If all of these conditions are met, then every minting round 70% of the SV rewards received by alice::1220abc...def will be added to the existing lock, and the remaining 30% will be minted as liquid CC owned by alice::1220abc...def.
Example: Single Beneficiary Configuration
We expect SVs with a single beneficiary party to run this minting automation with the target percentage set to the tier they are aiming for; and adjust that percentage once a year when the tier locking requirement is reduced.
Example: Multi-Beneficiary Configurations
SVs with multiple beneficiaries have two kinds of options for how to automate the locking of newly minted SV rewards. They can either configure their beneficiaries such that there's a single beneficiary that locks all their required SV rewards, or they can ask each beneficiary to lock the required percentage.
Automatic Enforcement of SV Underlocking
We propose to add SV node automation that automatically enforces the temporary and permanent loss of SV weight per the rules defined in CIP-0105 with the one modification that the temporary loss of SV weight is immediately enforced upon detection. The implementation depends on the implementation of the CIP proposal to move SV weight management fully on-ledger proposed by IntellectEU (design doc). The implementation further requires:
- adding new configuration parameters: for the weight schedule, the activation of on-chain enforcement of SV locking, the termination time of the SV lock-up requirement, and the grace period for permanent loss of SV weight. They can all be changed by SV voting.
- tracking of newly minted SV rewards on-chain: once the activation time of on-chain SV locking enforcement is past, minting SV rewards creates SV-mint-receipt contracts that are used by the SV node automation to track changes to an SV's lifetime rewards. They are created both for normal SV rewards and milestone rewards. These contracts are automatically merged by SV automation to keep their number constant.
- storing historic SV reward totals on-chain: for every SV rights owner the total lifetime rewards earned prior to activating on-chain SV locking enforcement are stored on-chain. These values are set using SV voting.
- on-chain enforcement of underlocks: extend per-SV state to track underlock events and their permanent-loss deadlines. Reward coupon creation immediately used the adjusted weight. The loss of weight becomes permanent if not recoevered within the grace period.
- off-chain automation to detect and enforce underlocks: extend the SV node automation to reconcile at least once per round each SV’s on-chain weight adjustment against the weight adjustment warranted based on the weight schedule, the total locked amounts, and their lifetime rewards.
- automatic termination of SV lock-up requirement:
the termination of the SV lock-up requirement is measured against the
OpeningMiningRound.issuingForparameter, which determines the timepoint on the issuance curve for the round. By default the termination time of the SV lock-up requirement is set to 5 years and 30 days, which corresponds to 30 days after the next halving as specified in CIP-0105. Once it is reached the SV node automation stops enforcing SV underlocks. Holding SV locks is thus no longer required and they can be unlocked by their funds owners to start accessing their funds according to the vesting schedule.
The bulk of this implementation consists of complex, but purely technical changes to SV node automation, the Daml code for DSO governance, and the Daml code for SV reward coupon creation and minting. From a business-level perspective, the key aspect is how the grace periods work, which we illustrate in the examples below.
SV Right Owner to SV Node Operator Relationship
Recall that Automated enforcement depends on the change to move SV weight management on-chain. That change introduces an on-chain representation of all SV rights, which records both the SV weight for a given SV right, and the SV node operator hosting the right and driving SV reward coupon creation for it.
Note that there’s the following special stipulation in CIP-0105:
For any organization that has or does operate more than one Super Validator, the lock up calculation is based on the aggregate lifetime earnings of any/all operating SVs. Any reduction in weighting will apply to total SV Weight for that organization. For the avoidance of doubt this provision applies to SVs identified as Digital-Asset-1, Digital-Asset-2 Cumberland-1 and Cumberland-2.
To handle that stipulation, we propose to add the notion of an optional ultimate SV rights owner to on-ledger SV right representation. If not set, then the SV rights owner is considered the ultimate SV rights owner. All computations that determine underlocks always aggregate locks, lifetime rewards, and weights for the ultimate SV rights owner.
Example: Digital Asset SV Right Configuration
One configuration option for the nodes operated by Digital Asset is as follows: split the total weight across the two SV rights “Digital-Asset-1” and “Digital-Asset-2”. Each of them is assigned to its own node, which is responsible for the coupon creation of its weight. Name “Digital-Asset” as the ultimate SV right owner on both of these SV rights, and create the SV locks with the lock subject “Digital-Asset”.
In case of an underlock, the enforcement will happen on-chain by modifying both SV rights.
Example: Temporary Loss of Reward Weight
Assume there’s an SV right owner named ExampleSV onboarded with weight 10. Assume further that they use party A to receive rewards, and that they were onboarded after on-chain SV locking enforcement was activated. Their reward state is:
- SV Reward State:
- SV name:
ExampleSV - SV beneficiaries:
100%forA - weight: 10
- lifetime rewards before on-chain enforcement: 0 CC
- SV name:
Assume that the next open round is r and the SV node hosting ExampleSV triggers reward coupon creation, which results in:
- SV Reward State:
- SV name:
ExampleSV - SV beneficiaries:
100%forA - weight: 10
- lifetime rewards before on-chain enforcement: 0 CC
- SV name:
- SV Reward Coupon:
- round:
r - weight:
10 - SV name:
ExampleSV - beneficiary: A
- round:
Once A mints this coupon, the results are:
- payout of
10 * (issuancePerSvWeight for round r)CC toA - SV Reward Mint Receipt
- amount:
10 * (issuancePerSvWeight for round r) - SV name:
ExampleSV
- amount:
The SV node automations include the mint receipt in their computation of lifetime rewards. They thus detect that ExampleSV is underlocked at t1 and update the on-chain reward state to:
- SV Reward State:
- SV name:
ExampleSV - SV beneficiaries:
100%forA - weight: 10
- lifetime rewards before on-chain enforcement: 0 CC
- temporary adjustment:
0% - permanent adjustment schedule:
0%aftert1 + 30 days
- SV name:
The permanent adjustment schedule stores the deadlines after which the reward weight used for coupon creation is permanently adjusted to X% of the full weight. The temporary adjustment has no associated schedule, as it is effective immediately. Coupon creation always uses the smallest adjustment that is in effect.
In the next round, the temporary 0% adjustment is in effect and the SV reward coupons stop being created for ExampleSV. The owners of ExampleSV realize they need to lock funds to continue earning rewards. They organize funding and lock the minimum lock amount of 10k CC for their SV, which results in:
- SV Lock:
- lock owner:
A - lock subject:
ExampleSV - amount: 10k CC
- lock owner:
The SV node automations detect that ExampleSV qualifies for Tier 1, and update the rewards state to:
- SV Reward State:
- SV name:
ExampleSV - SV beneficiaries:
100%forA - weight: 10
- lifetime rewards before on-chain enforcement: 0 CC
- temporary adjustment: none
- permanent adjustment schedule: none
- SV name:
Thus the reward adjustment schedules are cleared and ExampleSV starts earning full rewards again.
Example: Permanent Loss of Reward Weight
Assume that in the prior example, the ExampleSV already had lifetime earnings of 1M CC and their initial rewards state is:
- SV Reward State:
- SV name:
ExampleSV - SV beneficiaries:
100%forA - weight: 10
- lifetime rewards before on-chain enforcement: 1M CC
- SV name:
After the underlock is enforced by SV automation at t1, their reward state is:
- SV Reward State:
- SV name:
ExampleSV - SV beneficiaries:
100%forA - weight: 10
- lifetime rewards before on-chain enforcement: 1M CC
- temporary adjustment:
0% - permanent adjustment schedule:
0%aftert1 + 30 days
- SV name:
Assume that at t1 + 10 days, they lock 450k CC, i.e., they qualify for Tier 2 that earns 60% of the weight. After the SV automation runs, the new on-chain state is:
- SV Lock:
- lock owner:
A - lock subject:
ExampleSV - amount: 450k CC
- lock owner:
- SV Reward State:
- SV name:
ExampleSV - SV beneficiaries:
100%forA - weight: 10
- lifetime rewards before on-chain enforcement: 1M CC
- temporary adjustment:
60% - permanent adjustment schedule:
60%aftert1 + 30 days
- SV name:
Note that the permanent adjustment schedule is not fully reset, as ExampleSV was underlocked at Tier 2 already since t1. Thus the temporary 60% adjustment to their rewards applies and they only earn 60% of their weight.
Note that the 0% underlock deadline was removed, which reflects that recovery is immediate upon locking funds that qualify an SV for a higher tier.
Assume that at t1 + 20 days, they unlock 100k CC, which implies that a lock of 350k CC remains. They thus only qualify for Tier 3 that earns 40% of their weight. The resulting reward state is:
- SV Reward State:
- SV name:
ExampleSV - SV beneficiaries:
100%forA - weight: 10
- lifetime rewards before on-chain enforcement: 1M CC
- temporary adjustment:
40% - permanent adjustment schedule:
60%aftert1 + 30 days40%aftert1 + 20 + 30 days
- SV name:
Note that the 40% underlock deadlines start at t1 + 20 days, which is 20 days later than the 60% underlock deadlines.
This reflects that the locking at 60% recovered the 0% underlock, even though it happened less than 30 days ago.
After t1 + 30 days, the 60% weight reduction becomes permanent.
We can see this by looking at the example where ExampleSV increases their locks to 700k CC and would thus in principle qualify to earn 100% of their rewards. Their new reward state becomes:
- SV Reward State:
- SV name:
ExampleSV - beneficiaries:
100%forA - weight: 10
- lifetime rewards before on-chain enforcement: 1M CC
- temporary adjustment: none
- permanent adjustment schedule:
60%aftert1 + 30 days
- SV name:
Note that the permanent deadline that is still in the future is removed.
However the permanent deadline that is already in the past remains effective,
and the ExampleSV thus permanently only earns 60% of their SV weight.
Delivery Plan and Migration Plans
Incremental Delivery Plan
We propose an incremental delivery that focuses first on on-chain enforcement of locks and their vesting schedules, and then on reducing the operational overhead for managing them. Concretely, we propose the following increments of the features specified in the High-Level Specification:
- Compatibility mode for SV and FA locks: implement all operations of the compatibility mode for managing SV and FA locks using the TSv1 APIs. Locks must always lock an SV determined minimal amount of CC.
- Provisional FA locks: implement support for provisional FA locks; and add SV automation to convert provisional into full FA locks once the corresponding featured app right is created.
- Support custom controllers: allow creating SV and FA locks with custom controllers; and add an API for wallets and staking apps to support all operations on such locks.
- Automatic enforcement of FA underlocking: FA underlocks are tracked and automatically enforced after a seven day grace period.
- SV lock top-up automation: allow minting SV rewards directly in locked form into an existing SV lock; and extend the minting automation of validator nodes to make use of that.
- Automatic enforcement of SV underlocking: SV lifetime rewards are tracked on chain and used to detect SV underlocks. SV nodes run automation that enforces both temporary and permanent weight changes on-chain until the SV lock-up requirement terminates.
Migration to On-Chain Enforcement of SV Locks
We propose to incrementally move the enforcement of SV locks on-chain:
- Wait for Increment 1 to go live on MainNet: this is required for SVs to effectively manage on-chain SV locks.
- Adjust foundation dashboards to include on-chain locks: as soon as Increment 1 is live on DevNet, the foundation adjusts their dashboards to track both on-chain locked CC and good-faith locked CC for SV locks.
- Require on-chain SV locks after 30 days: once Increment 1 is live on MainNet, the SV locks must be transitioned to on-chain locks within 30 days. They use their TSv1 wallets to do so.
- Relieve SVs of manual top-ups and improve tax efficiency: once “SV lock top-up automation” is delivered, SVs can adjust their configuration of beneficiaries and SV reward minting to automatically mint the required amount of SV rewards in locked form.
- Relieve foundation of SV lock weight enforcement: once “Automatic enforcement of SV underlocking” is delivered to MainNet, the foundation enables this in three steps:
- They create an SV vote to activate the on-chain enforcement of SV underlocking.
- Once that is activated at time
t, they use their dashboards to determine the lifetime SV rewards up to timetand reflect that on-chain by creating corresponding SV votes. - Once all lifetime rewards have been reflected on-ledger, the foundation can stop monitoring and enforcing SV underlocks via manual SV votes.
Note that in Step 1, the SVs can create and manage their locks using any TSv1 wallet with support for two-step transfers. They will be able to use substitutions and transfers as soon as the “Substitution and Transfers for SV and FA locks” feature lands on MainNet and their wallets support those workflows through a full-feature integration.
Migration to On-Chain Enforcement of FA Locks
We propose to incrementally move the enforcement of FA locks on-chain:
- Wait for Increments 1-3 to go live on MainNet: these are required for FA operators and staking apps to effectively manage on-chain FA locks.
- Adjust foundation dashboards to include on-chain locks: concurrently with the development of Increments 2 and 3, the foundation adjusts their dashboards to track both on-chain locked CC and good-faith locked CC for FA locks.
- Require on-chain FA locks after 30 days: once increments 1-3 are live on MainNet, the FA locks must be transitioned to on-chain locks within 30 days.
- Relieve foundation of FA lock enforcement: once “Automatic enforcement of FA underlocking” goes live on MainNet the foundation can stop monitoring and enforcing FA underlocks via manual SV votes.
Technical Specification
The subsections within this technical specification provide additional details on implementation aspects relevant to the integration of governance locks with wallets or apps. They rely on the full high-level specification as context, and where possible they refer to code of the Reference Implementation to avoid duplicating technical details.
Controller Consensus on Withdrawal and Unlock Times
Withdrawing vested funds requires passing in the timepoint up to which the vested funds are computed, which must be in the past. When authorization from multiple vesting controllers is gathered in multiple steps, each step may come with its own timepoint. These timepoints are accumulated in a way that maximizes the benefit of the funds owner: for withdrawal, the timepoints are combined into the time of withdrawal by taking the latest timepoint.
The same concern applies to unlocking, which requires passing in the timepoint as of which vesting starts. This timepoint must be in the future. For unlocking, the timepoints from separate authorizations are combined into the actual vesting start point by:
- Taking the earliest timepoint that is still in the future when the last required controller approves the unlock.
- Restricting that timepoint to be no more than 24h in the future.
The second constraint is required to avoid a corner case where the last controller passes in a timepoint that is very far in the future.
Wallet Integration Concerns
Holding Display and Selection
Locked holdings with expired locks (code) must be treated as unlocked holdings, i.e., they should be included in the available total supply and they should be used to fund transfers and other actions that require input holdings. Doing so ensures that fully vested holdings can always be used as funding as soon as they vest.
This concern is not specific to this CIP. It already applies to working with the locked holdings backing an expired token standard allocation or two-step transfer. We call it out here to ensure that wallet providers are aware of it.
New Network Configuration Parameters
This CIP introduces multiple new network configuration parameters that govern the behavior of governance locks and can be changed using SV voting. All of them are called out explicitly in the high-level specification section which they affect. A full listing of them together with their concrete names can also be found in the reference implementation here.
Motivation
This CIP serves to align the stakeholders of CIP-0105 (SV Locking) and CIP-0116 (FA Locking) on the implementation of on-chain enforcement of SV and FA locking. It is motivated by the fact that the concrete mechanisms chosen to implement locking as well as their delivery timelines have a material impact on the network ecosystem in general and the operability of SVs, FAs, and staking apps in particular.
Rationale
The business rationale for SV and FA locking and their vesting and underlock enforcement was already given as part of CIP-0105 and CIP-0116. Where we had to make design choices for this CIP, we optimized for the following priorities:
- Transition to on-chain SV and FA locks as quickly as possible.
- Enable credit markets for SV and FA locks (e.g., staking apps) to be built.
- Minimize the confusion of users using wallets to interact with locks.
- Minimize the delivery cost of the implementation.
Alternatives Considered
The above priorities also reflect in the following alternatives that we considered and rejected for particular implementation choices.
- Rely on full-feature integration exclusively: building only one interface between wallets and governance locks would lower the implementation cost, but it would delay the transition to on-chain SV locks until all of their custodians have completed a full-feature integration. Adding the compatibility mode accelerates the transition at acceptable cost.
- Allow substitutions to change the lock subject: technically the substitution operation could also allow changing the lock subject, which would provide interesting flexibility to funds providers. However it likely would reduce the total value locked on-chain. For example, FA providers would have way less “skin in the game”, as they could just walk away from their current FA right, and transition their locked funds to a new FA party with a new right.
- Make transfers a special case of substitutions:
in principle, substitutions could specify both a
substitutedAmountthat determines the funds provided by the new owner and atransferAmountthat determines the funds being transferred from the current owner to the new owner. This would technically allow transfers to be modeled as substitutions. However it makes accepting a substitution risky, as the new owner could attempt to pretend that they are substituting funds when in fact they are also claiming funds from the existing owner. Requiring the existing owner to initiate the transfer of their funds removes that risk. - Support app-specific lock metadata: the benefits of associating application-specific metadata with locks do not justify the added complexity for specifying lock substitutions and transfers, which require specifying the metadata for both target and new locks. If a concrete need arises, this support can be added in a future change.
- No grace period for permanent removal of underlocked FA rights: CIP-0116 stipulates that “If locking falls below required thresholds, Featured App status is immediately removed.” Enforcing this strictly would imply that a single operational mistake on a single FA lock would make an FA provider lose their FA status and force them to go through the manual process of reapplying for it. We consider this unnecessary operational overhead, which is why this CIP proposes to immediately suspend the FA status on underlocking, but only permanently revoke it after a grace period.
- Switch SV reward minting flows to TBAR: there were initial considerations of switching SV rewards minting to use the same off-ledger computations as the ones used for traffic-based app rewards. This would allow for slightly less delayed underlock enforcement, as it could be computed exactly as of round start instead of being delayed by about 30s. However the implementation effort for doing this switch is significantly higher than the one for adapting the existing SV reward minting flow.
- No minimum lock amount: the minimum lock amount requirement does complicate the operations of staking apps and it would be great to not have it. However without a minimum lock amount there’s a risk that staking apps do produce lots of small locks. A situation that’s similar to how some wallets used to produce lots of “dust” CC holdings, e.g., as part of marketing campaigns. Every lock does consume resources on SV nodes. A minimum lock amount avoids having to spend delivery resources on scalability problems resulting from “dust locks”.
- Unvested release of funds upon termination of the SV lock-up requirement: we considered releasing the funds in (vesting) SV locks immediately upon termination of the SV lock-up requirement. We decided against this approach in favor of the vesting-based release of SV lock funds, which allows for a clearer and more orderly operational transition to the next phase of governance.
Reference Implementation
The reference implementation for the Daml code is currently (Oct 9, 2026) being built as a stack of PRs on this Splice feature fork:
The work is progressing along the Incremental Delivery Plan. See the open and closed PRs to track the progress of the reference implementation.
Copyright
This CIP is licensed under CC0-1.0: Creative Commons CC0 1.0 Universal.
Changelog
-
Oct 14, 2026:
- clarified rationale wrt termination of SV lock-up requirement
-
Oct 9, 2026:
- simplify termination of SV lock-up by just stopping enforcement
- remove grace period for temporary loss of SV weight
- removed support for app-specific lock metadata
- remove explicit top-up operations in favor of the simpler and more general automatic lock merging
- refactored compatibility mode to support transfers and substitutions of owner-controlled locks
- require passing explicit time parameters in compatibility mode and remove the heuristic to determine them
- adjust incremental delivery plan to deliver transfers and substitutions as part of Increment 1
- clarify SV and FA locking migration plans wrt their delivery dependencies
-
Oct 7, 2026:
- replace CIP number placeholders with the assigned CIP number: CIP-0127
- specify lock transfers
- specify that substitution and transfer proposals expire after 90 days
- make CIP independent of whether TSv2 is the API used for full feature integrations
-
Sep 3, 2026: Incorporated review feedback:
- automate termination of SV lock-up period
- aggregate SV weight across multiple nodes operated by the same SV
- change "withdrawal controllers" to vesting controllers
- switch to specifying substitution targets by key instead of by contract-id.
-
Aug 28, 2026: Initial draft created.