Tranfr — Programmable Recovery for CKB

A programmable safety net for CKB self-custody

Applicant: SalmanDev (Developer behind Fiber checkout) Funding requested: $1,600 USD Timeline: 8 weeks Category: CKB Infrastructure / Security / Self-Custody

1. Summary

On CKB today, non-custodial funds answer to exactly one key. Lose it and the money is gone forever. Share it and you’ve handed one person total control. And there is no native, on-chain way to pass assets to an heir without doing one of those two things.

Tranfr adds a programmable recovery path: stay active and retain full control, but if you stop checking in for a defined period, a designated recovery recipient can claim the funds — enforced on-chain, without a custodian. A user creates a Tranfr, designates a recovery recipient, and sets an inactivity period (90, 180, or 365 days, or custom) fixed at creation. While the Tranfr is active, the owner can send a heartbeat to reset the timer, or update the recovery recipient — which also resets the timer. If no heartbeat or policy update occurs before the deadline, the owner path freezes and the recovery path becomes eligible: the recipient’s signature, combined with the expired timelock, becomes sufficient to claim the assets.

The mechanism is enforced entirely by CKB scripts and transaction rules — no custodian, oracle, or centralized service is involved.

This grant is scoped to validating and implementing a reusable CKB recovery primitive,then providing the SDK and a minimal user-facing testnet demo that proves the full recovery flow is usable and integrable.

2. The Problem

A private key can be lost, destroyed, forgotten, or held by someone who becomes incapacitated or unavailable. When that happens, the blockchain has no way of knowing the legitimate owner is gone — the Cell and its assets remain valid, but nobody may be able to satisfy the spending conditions.

Existing approaches each have tradeoffs: multisig requires multiple parties during normal operation; custodians introduce a trusted intermediary; manual inheritance arrangements rely on off-chain coordination; application-specific recovery systems aren’t reusable; and a conventional single-key lock has no fallback at all.

CKB provides programmable ownership conditions and transaction-level timelocks through since. Tranfr uses these existing primitives to build a recovery path without any protocol changes.

3. How It Works

Terminology, used consistently throughout:

  • Owner — the person currently controlling the Tranfr.

  • Recovery recipient — the person/address designated to receive the assets if the owner becomes inactive.

  • Heartbeat — an owner-signed transaction confirming continued control; resets the timer, leaves policy unchanged.

  • Policy update — an owner-signed transaction changing the recovery recipient, inactivity period, or other supported parameters; resets the timer and changes policy.

  • Recovery transaction — executed by the recipient after expiry.

Setup. The owner sets a recovery recipient and an inactivity period. The inactivity period is fixed at creation for this version — only the recovery recipient can be changed later via a policy update.

Active state. While active, the owner retains full control: they can heartbeat at any time to reset the timer without changing anything, or issue a policy update to change the recovery recipient, which also resets the timer. Ordinary CKB transactions (spends, Fiber activity, DeFi interactions) do not count as activity and do not reset the timer only an explicit heartbeat or policy update does. This avoids ambiguity about what constitutes “still active” and keeps the contract logic simple to audit.

Expiry. This is the critical state transition. Once the inactivity period lapses without a heartbeat or policy update, the recovery policy becomes eligible and can no longer be modified by the owner the owner can no longer heartbeat, change the recipient, or extend the timer. Eligibility isn’t automatic execution: the Cell isn’t transferred on its own. The recovery recipient must submit a recovery transaction, which the script validates against two conditions — the recipient’s signature, and the since-enforced expiry, both of which must hold. Before expiry, the recipient’s signature alone is insufficient regardless of validity; after expiry, it becomes sufficient. No centralized approval is needed either way.

4. Technical Architecture

Tranfr Lock Script — a lock script for CKB-VM with two logical operation groups:

  • Owner operations (while active): normal spend, heartbeat, and policy update (recovery recipient only, in this version). Heartbeat and policy update are kept as distinct state transitions — a heartbeat only resets the timer, a policy update changes the recipient and resets the timer. Keeping these separate.

  • Recovery operation (after expiry): a recovery transaction validated against the recipient’s signature and the elapsed since condition.

Key technical point: the owner-side freeze after expiry is enforced by an independent chain-time check in the lock script (via a header dependency), not by since alone — since only gates the recovery path’s minimum-time condition. Milestone 1 validates this mechanism against CKB’s actual transaction/header semantics before the lock script implementation is finalized, and functions as a hard technical gate: if the header-time mechanism cannot establish the required irreversible expiry invariant under CKB semantics, the implementation design is revised before substantial coding begins.

Policy state. Each Tranfr commits to an owner, a recovery recipient, an inactivity period (fixed at creation), and a recovery deadline. The inactivity timer represents the maximum permitted period since the owner’s last valid heartbeat or policy update — nothing else resets it.

TypeScript SDK — createTranfr(), getTranfrPolicy(), getTranfrStatus(), buildHeartbeat(), buildPolicyUpdate(), buildRecovery(), isRecoveryEligible(). Handles transaction construction while keeping the underlying policy transparent and independently verifiable.

State machine:

CREATE → ACTIVE ⇄ (heartbeat / policy update, timer resets)

ACTIVE → (no activity before deadline) → EXPIRED → RECOVERY ELIGIBLE → RECOVERY TX → CLAIMED

5. Security Model

The recovery recipient cannot claim before expiry — the since timelock is enforced at the script level regardless of signature validity. After expiry, the owner path is frozen by an independent chain-time check in the lock script rather than by since alone: the script rejects any owner heartbeat or policy update once current chain time passes the stored deadline, so the recovery state is immutable once reached.

While the Tranfr is active, the owner is intentionally authorized to modify the recovery, this means the owner retains full control over who can eventually recover the assets for as long as they’re demonstrably present and active. That authority ends the moment expiry is reached.

Securing the recovery recipient’s own key is the recipient’s responsibility, on the same terms as any other CKB key — Tranfr doesn’t introduce new custody requirements on that side, it uses the recipient’s existing signing capability.

This POC is scoped to a single designated recipient as the entire v1 model. Social or threshold multi-party recovery is real added complexity, better suited to a future development if the core primitive is validated.

The entire security boundary is the CKB lock script itself. Since this POC has no off-chain notification component, there’s no off-chain trust surface to reason about in this phase — every guarantee is enforced by the script and independently verifiable.

6. Deliverables

  • Tranfr Lock Script — Implement with owner operations (spend, heartbeat, policy update) and the recovery operation, plus a comprehensive test suite covering both state transitions, adversarial cases, expiry-boundary conditions, and the frozen-after-expiry invariant.

  • Tranfr SDK — open-source TypeScript library for policy creation, inspection, heartbeat and policy-update construction, recovery transaction construction, and status calculation.

  • Testnet Demo — > Testnet Demo / Web UI— A minimal user-facing web application deployed on CKB testnet demonstrating the complete Tranfr lifecycle: create a Tranfr, designate a recovery recipient, send a heartbeat, update the recovery recipient, observe expiry, and execute recovery. The interface will expose the relevant Tranfr state and transaction status at each stage so reviewers and community can independently verify the recovery flow.

  • Documentation — technical specification, security model, threat model, transaction flow, integration guide, test vectors.

All code will be open-sourced.

POC Scope

one owner + one recovery recipient + one immutable inactivity period + heartbeat + recipient update + recovery.

Out of scope for this grant:

Multiple recovery recipients, social recovery, configurable activity sources, automatic notifications, production wallet integration, production security audit

7. Roadmap

Milestone Timeline Work
1. Protocol & feasibility Week 1 State machine, threat model, transaction model, and proof that expiry/freeze semantics work as intended. If the header-time expiry-enforcement mechanism cannot be validated against CKB semantics, the implementation design is revised before substantial coding begins.
2. Core CKB primitive Weeks 2–4 Lock script + owner ops (spend/heartbeat/policy update) + recovery op + owner-side expiry check + adversarial and expiry-boundary tests
3. SDK Weeks 5–6 Minimal TypeScript SDK for policy creation, inspection, and transaction construction
4. Testnet validation Week 7 Deploy a minimal web UI on CKB testnet exercising the complete lifecycle: create, heartbeat, recipient update, expiry, and recovery
5. Release Week 8 Documentation, test vectors, integration example, UI/demo refinement, and open-source release

8. Budget — $1,600 USD

Area Amount
CKB scripts + policy/state enforcement + testing $1,050
TypeScript SDK $350
Testnet demo + documentation $200

9. Success Criteria

A developer should be able to: create a Tranfr, set a recovery recipient and inactivity period, change the recipient while active and verify the timer resets, send a heartbeat and verify it resets the timer without altering policy, allow the timer to expire, verify that both heartbeat and policy updates are rejected after expiry, execute recovery with the designated recipient, and confirm recovery is invalid before expiry and valid after. Most importantly, a third-party wallet or application should be able to integrate Tranfr via the SDK without depending on the reference application’s own architecture.

The critical security test: an owner must not be able to extend or modify an expired Tranfr by submitting a heartbeat or policy update after the recovery deadline has passed.

10. Ecosystem Value

Tranfr is built as a primitive that any CKB wallet or app can integrate, rather than a single-purpose inheritance app. The same mechanism can eventually support personal wallet recovery, inheritance, business continuity, DAO treasury recovery, lost-device protection, and long-term cold storage — but the first version implements only the simplest model: one owner, one recovery recipient, one inactivity period fixed at creation, with recipient-only policy control while active. That keeps the POC achievable while establishing a foundation other applications can build on rather than reimplementing their own version.

CKB’s Cell Model and since mechanism make this possible without any protocol changes: ownership conditions and temporal conditions are both expressible directly on-chain, so the recovery guarantee doesn’t require a third party to enforce it.

5 Likes

I looked at the irreversible-expiry boundary more closely and would suggest the following CKB-native construction for the critical owner-freeze path.

Core correction

For an owner operation that is valid only before a stored deadline, I would not use a caller-selected header_dep as proof of current chain time.

Use a two-phase transition instead, so the time anchor is the actual commitment block of an intermediate Cell:

ACTIVE
  -> owner-signed BEGIN_OWNER_OP
       -> PENDING_OWNER_OP
            -> NORMALIZE -> ACTIVE
            -> RECOVER   -> CLAIMED

The first transaction does not apply the heartbeat or recipient update directly. It only commits to the requested operation.

PendingOwnerOp {
    version

    previous_state_hash
    previous_deadline_epoch
    inactivity_period_epochs

    owner_lock_hash
    previous_recovery_lock_hash

    operation_kind              // HEARTBEAT | RECIPIENT_UPDATE
    operation_payload_hash
    proposed_recovery_lock_hash

    state_nonce
}

BEGIN_OWNER_OP must enforce:

owner signature is valid

exactly one successor PENDING_OWNER_OP Cell exists

protected capacity / typed value is conserved exactly

previous policy is copied exactly

previous deadline is copied exactly

requested operation is committed by hash

no protected value is released

no recipient change is applied directly

no deadline extension is applied directly

Commitment-bound time decision

When PENDING_OWNER_OP is later consumed, the script loads the header associated with that exact input Cell:

ckb_load_header(..., input_index, CKB_SOURCE_INPUT)

The corresponding block header must be present in header_deps, but it is not being trusted as an arbitrary historical header selected by the transaction builder. With CKB_SOURCE_INPUT, the script resolves the block in which that input Cell was created.

Use one consensus metric end-to-end. For v1, an absolute epoch-number-with-fraction policy keeps the comparison deterministic:

begin_epoch  = epoch of the block that committed PENDING_OWNER_OP
old_deadline = previous_deadline_epoch

timely = epoch_less(begin_epoch, old_deadline)

The epoch comparison must use CKB epoch-fraction semantics, not a raw integer comparison of the encoded field.

Effective state

The semantic result is derived only from the commitment-bound begin_epoch:

if timely and operation_kind == HEARTBEAT:

    effective_recovery = previous_recovery

    effective_deadline =
        epoch_add(begin_epoch, inactivity_period_epochs)


if timely and operation_kind == RECIPIENT_UPDATE:

    effective_recovery = proposed_recovery

    effective_deadline =
        epoch_add(begin_epoch, inactivity_period_epochs)


if not timely:

    effective_recovery = previous_recovery

    effective_deadline = old_deadline

    owner_operation_effect = NONE

A late owner transaction can therefore create no new recovery authority and no new deadline.

NORMALIZE is only a canonicalization step. It must not accept a different policy, value set, deadline, or operation payload. For a late pending operation, normalization into a modified owner state is rejected; recovery proceeds under the previous policy.

The pending Cell itself must remain recoverable, so failure to normalize cannot trap the protected value.

Recovery path

Recovery uses the derived effective policy:

recovery signer = effective_recovery

input since =
    absolute epoch threshold
    exactly equal to effective_deadline

The lock verifies that the encoded since threshold matches the derived deadline. Consensus then supplies the lower-bound rule:

target_epoch >= effective_deadline

This creates the required asymmetry:

owner-side update:
    must have entered the chain before old_deadline

recovery:
    cannot enter the chain before effective_deadline

Pre-signed heartbeat test

1. owner signs BEGIN_OWNER_OP before deadline D

2. owner keeps the transaction offline

3. chain passes D

4. owner broadcasts the old transaction

5. PENDING_OWNER_OP is committed after D

6. begin_epoch >= old_deadline

7. owner_operation_effect = NONE

8. previous recovery recipient remains authoritative

9. previous deadline remains authoritative

The signature creation time is irrelevant.

Adding an unrelated old block to header_deps cannot change the result, because the decision uses the header associated with the actual pending input Cell.

Minimum blocking tests

TEST 01
BEGIN committed before D
-> operation is accepted
-> new deadline derives from the actual pending-cell commitment epoch


TEST 02
BEGIN signed before D but committed after D
-> owner operation has no effect
-> old recovery policy remains authoritative


TEST 03
late RECIPIENT_UPDATE
-> proposed recipient never becomes effective


TEST 04
arbitrary stale header added to header_deps
-> cannot substitute for the header associated with the pending input Cell


TEST 05
pending transition changes protected value, previous policy, or previous deadline
-> transaction fails


TEST 06
timely pending Cell is never normalized
-> recovery still becomes possible at the derived effective deadline


TEST 07
same operation is replayed against a later Tranfr state
-> previous_state_hash / state_nonce binding fails


TEST 08
reorganization and reinclusion
-> time anchor follows the canonical commitment block of the live pending Cell


TEST 09
epoch-fraction boundary
-> SDK and lock script derive identical ordering and threshold values

The core invariant is:

Owner authority is determined by when the owner state transition entered the canonical chain, not by when it was signed and not by an arbitrary header chosen by the owner.

One remaining scope boundary

If ordinary owner spends are also meant to become impossible after expiry, they need the same commitment-bound gate.

Otherwise the security claim should be narrower and explicitly state that irreversible expiry freezes heartbeat and recipient-update authority, while ordinary spending follows a different rule.

A single-step owner spend cannot obtain an upper-validity bound from since, because since is a lower-bound precondition.

References

CKB RFC 0022 — Transaction Structure / Header Deps

CKB RFC 0017 — Transaction Since Precondition

Nervos discussion — two-step pattern for an operation valid only before T

3 Likes

Wow, I really appreciate you looking into this. Thanks for the suggestion, it fixes the biggest open risk in the proposal with stale-header attacks on the freeze mechanism.

2 Likes

I saw @Ajay worked on something similar, it’s worth checking out:

3 Likes

Thanks for the pointer, took a look. Ajay is solving a different problem and approach.

It’s a fixed-date vault (lock funds, unlock at one target block/timestamp), no check-in or reset mechanism. Tranfr’s core piece is the inactivity-based check-in: owner can keep extending indefinitely via heartbeat, and the hard part is making that freeze irreversible once expiry hits without letting the owner backdate a late operation.

Different primitive, adjacent category. Good to have on the radar though, appreciate you flagging it.

1 Like

Second boundary: ordinary owner spending must commit the exact effect before expiry

The remaining owner-side boundary is ordinary spending.

The proposal currently requires all three of the following:

  1. while active, the owner retains normal spending authority;
  2. an ordinary spend does not reset the inactivity timer;
  3. after expiry, the owner path is irreversibly frozen.

Those requirements are consistent only if an ordinary spend is no longer a single-step owner-signature path. A transaction signed before D but committed after D cannot be treated as timely, and since cannot provide an upper-validity bound.

The safe construction is to separate owner authorization from value release:

ACTIVE
  -> owner-signed BEGIN_SPEND
       -> PENDING_SPEND
            -> SETTLE_SPEND   if begin_epoch < old_deadline
            -> RECOVER        if begin_epoch >= old_deadline

BEGIN_SPEND does not transfer protected value to the intended recipient. It only places the exact intended spend effect on-chain.

Pending state

For a minimal v1 transfer profile, the pending state should contain the previous policy together with a complete, canonical spend plan:

PendingSpend {
    version

    previous_state_hash
    previous_deadline_epoch
    inactivity_period_epochs

    owner_lock_hash
    recovery_lock_hash
    state_nonce

    spend_plan_hash
    protected_value_commitment
}

SpendPlan {
    domain_separator          // TRANFR_SPEND_V1
    source_state_hash
    source_state_nonce

    ordered_outputs           // full CellOutput + outputs_data
    residual_output_index     // optional
    exact_fee_capacity
}

The full SpendPlan must remain available from the pending state itself, or from an immutable Cell whose outpoint and data hash are committed by PENDING_SPEND. Storing only a hash is not sufficient: if the owner disappears after BEGIN_SPEND, the recovery recipient or another executor must still be able to reconstruct and settle the authorized effect without obtaining an off-chain preimage from the owner.

The output commitment must cover the complete serialized output objects:

capacity
lock script
type script
output data
ordering

Committing only to a recipient and amount would leave room to substitute type state, change data, alter residual ownership, or move value through an uncommitted output.

BEGIN_SPEND rules

BEGIN_SPEND must enforce:

owner signature is valid

exactly one PENDING_SPEND successor exists

previous policy is copied exactly
previous deadline is copied exactly
state identity and nonce are bound

all protected capacity and typed value remain inside the pending state
no protected value is released in BEGIN_SPEND

the complete SpendPlan is canonical and available on-chain
spend_plan_hash matches the canonical plan bytes

the plan is satisfiable from the protected input
the fee is exact, not open-ended
any residual output preserves the Tranfr policy and old deadline

no heartbeat effect occurs
no recipient update occurs
no deadline extension occurs

For v1, the safest transfer profile is deliberately narrow:

one pending Tranfr input
no mutable external input dependency
no uncommitted output
no arbitrary callback
fee funded from the committed plan
exact output set

This gives the second phase a deterministic result that any party can submit.

Commitment-bound time decision

When PENDING_SPEND is consumed, the lock loads the header associated with that exact input Cell:

ckb_load_header(..., input_index, CKB_SOURCE_INPUT)

Then:

begin_epoch = epoch of the block that committed PENDING_SPEND
timely      = epoch_less(begin_epoch, previous_deadline_epoch)

As in the heartbeat/update path, the comparison must use CKB epoch-number-with-fraction semantics.

The signature time and broadcast time are irrelevant. The only deciding fact is when the pending state entered the canonical chain.

Timely spend

If:

begin_epoch < old_deadline

the owner committed the spend while authority still existed.

The second transaction may therefore execute only the exact committed plan:

SETTLE_SPEND:
    spend_plan_hash matches
    outputs match ordered_outputs exactly
    outputs_data match exactly
    protected value allocation matches exactly
    transaction fee equals exact_fee_capacity
    no additional protected route is introduced

The second phase should not require another owner signature. The owner already authorized the exact effect in BEGIN_SPEND. Making settlement permissionless prevents the protected value from becoming stuck if the owner disappears immediately after committing the pending state.

This does not preserve discretionary owner authority after expiry. It preserves only one already-fixed transition.

Any residual Tranfr output must carry:

same owner
same recovery recipient
same inactivity period
same old deadline
next state nonce

An ordinary spend therefore does not reset activity. If settlement occurs after the old deadline, the residual state is already recovery-eligible.

Late spend

If:

begin_epoch >= old_deadline

the spend plan has no effect.

The late pending state must not be able to:

release value
change a recipient
reduce the recoverable amount
change the recovery lock
extend the deadline
create a new owner-controlled residual

Only recovery under the previous policy is valid:

recovery signer = previous_recovery_lock
input since      = absolute epoch equal to old_deadline
recovered value  = the complete protected value

A transaction signed before D but kept offline and committed after D therefore cannot reserve a spend.

Recovery from a timely pending spend

A timely PENDING_SPEND must not expose a competing branch that allows the recovery recipient to ignore the committed outputs and redirect the full value.

The deterministic rule is:

timely pending spend
    -> exact committed spend must settle first
    -> only the residual remains under the old recovery policy

Because settlement is permissionless and the full plan is available on-chain, the recovery recipient can settle the plan themselves and then recover any residual whose old deadline has already passed.

This removes destination ambiguity:

before D:
    owner may commit one exact spend effect

after D:
    nobody may replace that effect with a different one

Scope boundary for Fiber and DeFi

A deterministic CKB transfer and an arbitrary Fiber or DeFi interaction are not the same transaction class.

A general application interaction may depend on:

other live Cells
changing liquidity
slippage bounds
counterparty inputs
callbacks
application-specific state transitions
mutable route composition

Those effects cannot safely be represented by an unconstrained generic SPEND flag.

For the POC, I would define ordinary spending as a canonical deterministic transfer profile. Broader Fiber/DeFi support should use a separate, integration-specific action commitment layer—potentially compatible with CoBuild-style action messages—where every integration defines exactly which fields may vary and which final invariants must remain fixed.

Until that layer exists, unsupported composable routes should fail closed rather than silently inherit ordinary owner authority.

Minimum blocking tests

TEST 01
BEGIN_SPEND committed before D
SETTLE_SPEND committed before D
-> exact outputs are accepted
-> residual keeps old deadline

TEST 02
BEGIN_SPEND committed before D
SETTLE_SPEND committed after D
-> exact committed outputs are still accepted
-> residual is immediately recovery-eligible
-> deadline is not reset

TEST 03
BEGIN_SPEND signed before D but committed after D
-> spend plan has no effect
-> full protected value remains recoverable

TEST 04
recipient, capacity, type script, lock script, output data, or output order is changed
-> settlement fails

TEST 05
fee differs from the committed fee
-> settlement fails

TEST 06
an extra protected output, input, callback, or uncommitted route is introduced
-> settlement fails

TEST 07
owner disappears after BEGIN_SPEND
-> a third party can settle the exact on-chain plan without an owner signature

TEST 08
recovery attempts to bypass a timely spend plan and take the full value
-> recovery fails
-> committed spend must settle first

TEST 09
residual state changes recovery policy or derives a new deadline
-> settlement fails

TEST 10
the same SpendPlan is replayed against a later Tranfr state
-> source_state_hash / state_nonce binding fails

The core invariant is:

Before expiry, the owner may commit an exact value transition. After expiry, the owner retains no open-ended authority to choose or modify a transaction.

And the recovery invariant is:

Recovery may claim the residual value, but it may not overwrite an exact spend effect that entered the canonical chain before expiry.

This should be fixed in the protocol model before ordinary spending is treated as a transparent owner operation in the lock script or SDK.

References

1 Like

Hi @SalmanDev,

Thank you for your continued interest in the Spark Program and for publishing the Tranfr — Programmable Recovery for CKB proposal.

After review by the Spark Program committee, your proposal has been assigned a status of Pending. This is not a rejection, but an invitation to revise.

The committee recognizes the value of a programmable recovery mechanism for CKB self-custody and appreciates the technical depth of your design. However, to make the solution accessible and verifiable, the committee would like to see the front-end UI completed.

Main Revision Recommendations:

  1. Complete the front-end UI: The current proposal focuses on CKB scripts, SDK, and testnet reference implementation. Please add a user-facing interface (e.g., a simple web app or testnet demo) that demonstrates the full recovery journey — creating a Tranfr, sending a heartbeat, updating the recovery recipient, and executing a recovery after expiry. This will help the committee and the community evaluate the usability of the recovery flow.

Please revise and resubmit based on the feedback above. We will arrange a new round of review as soon as possible. The committee would be happy to pre-test the demo once the UI is available.

Best,
xingtian
On behalf of the Spark Program Committee

cc: @Hanssen @yixiu.ckbfans.bit @zz_tovarishch

Thanks for the constructive feedback. The revised proposal is now updated accordingly. Thanks again.

2 Likes

Hi @SalmanDev,

We are pleased to inform you that the Spark Program Committee has approved the Tranfr proposal, with a funding amount of 700 USD (100% paid in CKB, 996.62 CKB/USD, 697,634 CKB; calculation: $700 * 100% @0.001003 = 697,634 CKB).

The committee recognizes the value of Tranfr as a programmable recovery primitive for CKB self-custody. We appreciate your clear design and the potential benefit to the ecosystem.

Here are the next steps:

  1. Complete the applicant information
    According to the latest standards of the Spark Program, applicants must provide at least their Discord account, email address, and a link to a GitHub repository already created for the proposed project. Although we know you are the developer of the fiber checkout project, please include this information in your response to improve the readability of the proposal post.

  2. Funding & Wallet Address
    The total grant is 700 USD (for the current cycle Spark grants are paid 100% in CKB).

    • The first installment (20%) will be disbursed as soon as possible.
    • Please provide the CKB wallet address to receive the funds.
    • The remaining 80% is flexible: it can be requested during weekly syncs as needed or claimed upon project completion.
  3. Weekly Sync
    We would like to establish a regular synchronization mechanism, with two options:

    • Text-based updates in this post, with progress updates at a fixed time each week and committee feedback in reply.
    • Or a brief video call.
      Please let us know your preference and a convenient time.
  4. Proposal Content Lock
    Once a proposal is approved, we will lock the current version of the proposal post as the reference baseline for subsequent delivery and acceptance. This is standard procedure for all approved Spark projects. If adjustments are needed during development, they can be discussed and documented during the weekly syncs.

Congratulations again, and we look forward to working together!

Best,
xingtian
On behalf of the Spark Program Committee

cc: @zz_tovarishch @Hanssen @yixiu.ckbfans.bit

1 Like

Thank you to the committee for approving the grant.

One thing before starting: the reduced funding of $700 (down from the original $1,600 request) lands at the same time the valuable feedback from Tianji surfaced a real gap in the design. The commit-bound two-phase construction needed to make the owner-freeze invariant actually hold (for both heartbeat/policy-update and ordinary spending) roughly doubles the state machine and test surface versus what I originally scoped, so at $700 I’d like to rescope the deliverable to match.

Proposed revised scope for $700:

  • Lock script implementing the corrected owner-op path only: BEGIN_OWNER_OP → PENDING_OWNER_OP → NORMALIZE / RECOVER, covering heartbeat and recipient-update with the commit-bound (CKB_SOURCE_INPUT header) time check from the review

  • Full adversarial and epoch-boundary test suite against that lock script (the pre-signed-and-withheld attack, late-normalize cases, replay protection, etc.)

  • Minimal testnet deployment exercising create → heartbeat → recipient-update → expiry → recovery for this path, so the fix is independently verifiable on-chain, not just on paper

  • Ordinary spending stays a single-step owner operation as proposed, outside this scope I’ll document that as a known limitation

Everything in it will be complete and independently verifiable rather than partial. Please let me know if this scope works. If you’d prefer something different, let me know.

Thank you.

2 Likes

Also here is the requested information.

1. Project repo

2. Wallet address

Ckb1qyqvaj4pmuw9vxurdlg8lx3sv7ywmcge33vsvrvu39

3. Weekly sync

Text updates in this thread work well for me. I’ll post a progress update every Monday covering: what was completed that week, what’s planned for the next, and any blockers.

Looking forward to seeing this project mature and continue adding value to CKB.

Hi @SalmanDev!

Regarding this update to the proposal, I have questions about this proposal’s original intent: what types of assets does this cover, and what capabilities does this provide to the owner?

One thing we should notice is that, for all the design you mentioned in this proposal, the owner always keeps the right to completely exit the Tranfr lock, which means it’s almost certain that the owner volunteered to allow the recipient to take their asset after the deadline. In that case, I see no reason to stop the owner from taking the assets, even after the deadline, especially considering all this complexity added for this. The simplest solution would be: ask the recipient to take the assets as soon as the deadline is reached.

Which also brings us to the second question: Does the enforced heartbeat mechanism really make sense here? You see, if the owner can always take the assets, they can simply transfer out everything to extend the deadline to forever. By limiting the longest duration, we do nothing but only deprive users who desire longer intervals of their choice.

Thus, I recommend you consider simplifying the design instead of complexing it. A reasonable solution would be to combine the time lock with the cheque lock, making the heartbeat mechanism an offchain agreement: the owner usually extends the deadline by 90 days, set in the frontend.

I do not consider it acceptable to sacrifice the SDKs and usability of front-end applications for the sake of making the protocol more complex, given that these peripheral components are essentially the fundamental guarantee of the protocol’s usability.

3 Likes

Thanks for the feedback. Alright, I’m dropping the hard-freeze model and the custom state machine entirely. Going with your suggestion: composing the existing time-lock with the cheque lock, and making the heartbeat an off-chain agreement (owner extends the deadline via the frontend, defaulting to 90 days as you proposed).

Hey @Hanssen, answering your first two questions with this edit since I missed them in my earlier reply:

Asset types: The scope covers CKB held in a Tranfr-locked Cell. Typed assets (UDTs, tokens) held in the same Cell would follow the same lock logic since the design doesn’t distinguish by asset type at the lock-script level, but this hasn’t been explicitly tested for v1, so I’d treat that as an open item rather than a confirmed guarantee for now. Fiber channel funds are out of scope for this POC.

Owner capabilities while active: The owner retains full control, normal spending, sending a heartbeat to reset the timer, and updating the recovery recipient (which also resets the timer). None of that requires the recipient’s involvement or any custodian approval.

3 Likes

Hi @SalmanDev,

The committee has completed the first payment. To avoid any ambiguity in our previous communications, we would like to reiterate here that the final acceptance criteria for the Tranfr project are the delivery of a fully functional frontend and SDK.

The first installment (139,527 CKB, 20%) have been disbursed. Transaction hash:
0x6d725557de503c75d3863778bac5ba13359eac3aabfb61a2e262e048cd1f5cde

Best,

xingtian

On behalf of the Spark Program Committee

cc: @zz_tovarishch , @Hanssen , @yixiu.ckbfans.bit

3 Likes

Hi, @SalmanDev,

We noticed that the first weekly update for the Tranfr project is overdue, so we’re reaching out to check on the latest progress.

First of all, we hope you and your team are doing well. Please be sure to take care of yourselves and maintain a healthy balance between work and rest.

From the project’s perspective, we’d appreciate an update on how things are going—whether progress is on track, whether you’ve encountered any obstacles, or how things are progressing overall. Even a brief update would help the committee and relevant teams stay informed and offer support if needed.

We understand that delays can occur for various reasons, and this is perfectly normal. What matters is sharing updates promptly—even when significant challenges or obstacles arise, making them public is extremely valuable. These experiences, including failures, are invaluable to the entire ecosystem. What we cannot accept is silence with no updates at all.

Please share the latest progress as soon as possible. If there are any challenges affecting the timeline, please feel free to contact us. The committee is willing to provide any assistance needed.

We look forward to your reply.

Best regards,
Xingtian

1 Like

Tranfr — Progress Update

I’ve made solid progress on the core Tranfr implementation and have now completed several of the key technical foundations.

Completed so far

  • Finalized the Tranfr data layout and state representation.

  • Implemented and tested the deadline-check logic in both Rust and TypeScript.

  • Measured real CKB network timing to validate the deadline assumptions against actual chain behavior.

  • Set up the CKB contract project and established the initial contract structure.

  • Added input validation and checks around the expected transaction/state structure.

  • Implemented and tested the owner unlock flow, including signature verification.

This gives me the core pieces needed to move from the initial protocol work into the recovery path and more comprehensive security testing.

Next steps

Next week I’ll focus on the remaining core contract functionality and security validation:

  • Implement recipient claims after the deadline.

  • Add additional safety and invariant checks.

  • Run comprehensive attack-scenario and expiry-boundary tests.

  • Deploy the contract to CKB testnet.

  • Work toward a reproducible/verifiable deployment so the deployed contract can be independently verified.

Once the contract side is stable, I’ll start the TypeScript SDK, including:

  • Encoders and decoders

  • Deadline and recovery-status checks

  • Status lookup

  • createTransfer transaction construction

After that, I’ll move on to the remaining functionality:

  • Renew / heartbeat flow

  • Reclaim / recovery flow

  • Minimal demo UI

  • Technical documentation and integration examples

The goal for this phase is to have the core Tranfr primitive working end-to-end on testnet, with the contract behavior independently testable and the SDK providing the interface needed for applications to integrate it.

1 Like

Hi @SalmanDev ,

Great to see Tranfr’s first-week update. Everything looks to be going smoothly so far—keep it up!

Best,
xingtian