Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# TimeCapsule 🕰️🔒

## Overview

TimeCapsule is a decentralized, secure time-locked asset vault built on the Stacks blockchain. It allows users to lock their STX tokens for a predetermined period, providing a robust mechanism for long-term asset management, controlled fund release, and beneficiary-based asset protection.

## 🌟 Key Features

### Time-Locked Asset Management
- Lock STX tokens for a customizable duration
- Minimum lock period: 1 day
- Maximum lock period: 1 year
- Flexible lock duration controls

### Beneficiary System
- Optional beneficiary nomination
- Fallback mechanism for asset recovery
- Grace period for beneficiary claims
- Secure beneficiary status management

### Advanced Vault Controls
- Extend lock periods
- Update beneficiary details
- Deposit and withdraw functionality
- Activity tracking and vault management

## 🛡️ Security Mechanisms

- Strict validation for lock periods
- Beneficiary status tracking
- Comprehensive error handling
- Contract-level access controls
- Immutable vault creation rules

## 🚀 Core Functions

### Vault Creation
```clarity
(create-vault
(lock-duration uint)
(beneficiary (optional principal))
(grace-period uint)
)
```

### Key Operations
- `deposit-stx`: Add funds to vault
- `withdraw-stx`: Withdraw after unlock period
- `extend-lock-period`: Prolong vault duration
- `update-beneficiary`: Change beneficiary details
- `claim-as-beneficiary`: Claim funds as nominated beneficiary

## 📋 Usage Scenarios

1. **Personal Savings Vault**
- Lock STX for future financial goals
- Set a long-term saving commitment

2. **Inheritance Planning**
- Nominate a beneficiary
- Ensure asset transfer if original owner is inactive

3. **Investment Lockup**
- Prevent impulsive withdrawals
- Enforce disciplined investment strategy

## 🔍 Technical Details

- **Blockchain**: Stacks
- **Language**: Clarity Smart Contract
- **Token Support**: STX (expandable)
- **Minimum Lock**: 1 day (144 blocks)
- **Maximum Lock**: 1 year (52,560 blocks)

## 🛠️ Installation & Deployment

### Requirements
- Stacks Wallet
- Web3 Compatibility
- Clarity Smart Contract Support

### Deployment Steps
1. Compile the Clarity contract
2. Deploy to Stacks blockchain
3. Interact via compatible wallet or interface

## ⚠️ Considerations

- Double-check lock periods
- Carefully select beneficiaries
- Understand grace period mechanics
- Keep track of vault activity

## 🔮 Future Roadmap
- Multi-token support
- Enhanced beneficiary features
- Improved activity tracking
- Potential integrations with DeFi protocols

## 🤝 Contributing
Contributions, issues, and feature requests are welcome!

## 💡 Disclaimer
Always review and understand smart contract mechanics before deployment.
125 changes: 114 additions & 11 deletions timecapsule/contracts/timecapsule.clar
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
;; TimeCapsule - Decentralized Time-Locked Asset Vault
;; A secure way to lock STX and fungible tokens with time-based release and beneficiary support
;; A secure way to lock STX and fungible tokens with enhanced beneficiary system

(define-data-var contract-owner principal tx-sender)

;; Constants for validation
(define-constant MIN-LOCK-PERIOD u144) ;; Minimum 1 day (assuming 144 blocks per day)
(define-constant MAX-LOCK-PERIOD u52560) ;; Maximum 1 year
(define-constant MIN-GRACE-PERIOD u144) ;; Minimum 1 day grace period
(define-constant DEFAULT-GRACE-PERIOD u4320) ;; Default 30 days grace period

;; Beneficiary Status
(define-constant BENEFICIARY-ACTIVE u1)
(define-constant BENEFICIARY-PENDING u2)
(define-constant BENEFICIARY-INACTIVE u0)

;; Vault structure
(define-map vaults
{ owner: principal }
{
amount: uint,
unlock-height: uint,
lock-duration: uint,
beneficiary: (optional principal),
beneficiary-status: uint,
grace-period: uint,
last-activity: uint,
token-type: (string-ascii 32)
}
)
Expand All @@ -22,6 +36,11 @@
(define-constant ERR-NOT-UNLOCKED (err u103))
(define-constant ERR-GRACE-PERIOD-EXPIRED (err u104))
(define-constant ERR-INSUFFICIENT-FUNDS (err u105))
(define-constant ERR-INVALID-LOCK-PERIOD (err u106))
(define-constant ERR-EXTENSION-TOO-SHORT (err u107))
(define-constant ERR-INVALID-BENEFICIARY (err u108))
(define-constant ERR-NO-BENEFICIARY (err u109))
(define-constant ERR-BENEFICIARY-NOT-ACTIVE (err u110))

;; Read-only functions
(define-read-only (get-vault-details (owner principal))
Expand All @@ -36,33 +55,107 @@
(>= current-height (get unlock-height vault)))
)

(define-read-only (get-remaining-lock-time (owner principal))
(let (
(vault (unwrap! (get-vault-details owner) u0))
(current-height block-height)
)
(if (>= current-height (get unlock-height vault))
u0
(- (get unlock-height vault) current-height)))
)

(define-read-only (can-claim-as-beneficiary (owner principal) (beneficiary principal))
(let (
(vault (unwrap! (get-vault-details owner) false))
(current-height block-height)
(grace-end (+ (get unlock-height vault) (get grace-period vault)))
(inactive-period (- current-height (get last-activity vault)))
)
(and
(is-eq (some beneficiary) (get beneficiary vault))
(is-eq (get beneficiary-status vault) BENEFICIARY-ACTIVE)
(or
(>= current-height grace-end)
(>= inactive-period (get grace-period vault))
)
))
)

;; Public functions
(define-public (create-vault (lock-period uint) (beneficiary (optional principal)) (grace-period uint))
(define-public (create-vault (lock-duration uint) (beneficiary (optional principal)) (grace-period uint))
(let (
(unlock-height (+ block-height lock-period))
(unlock-height (+ block-height lock-duration))
(actual-grace-period (if (< grace-period MIN-GRACE-PERIOD)
DEFAULT-GRACE-PERIOD
grace-period))
)
(asserts! (is-none (get-vault-details tx-sender)) ERR-VAULT-EXISTS)
(asserts! (and (>= lock-duration MIN-LOCK-PERIOD) (<= lock-duration MAX-LOCK-PERIOD)) ERR-INVALID-LOCK-PERIOD)

(map-set vaults
{ owner: tx-sender }
{
amount: u0,
unlock-height: unlock-height,
lock-duration: lock-duration,
beneficiary: beneficiary,
grace-period: grace-period,
beneficiary-status: (if (is-some beneficiary) BENEFICIARY-ACTIVE BENEFICIARY-INACTIVE),
grace-period: actual-grace-period,
last-activity: block-height,
token-type: "STX"
}
)
(ok true))
)

(define-public (extend-lock-period (extension-blocks uint))
(let (
(vault (unwrap! (get-vault-details tx-sender) ERR-NO-VAULT))
(current-height block-height)
(new-unlock-height (+ (get unlock-height vault) extension-blocks))
(new-duration (+ (get lock-duration vault) extension-blocks))
)
(asserts! (>= extension-blocks MIN-LOCK-PERIOD) ERR-EXTENSION-TOO-SHORT)
(asserts! (<= new-duration MAX-LOCK-PERIOD) ERR-INVALID-LOCK-PERIOD)

(map-set vaults
{ owner: tx-sender }
(merge vault {
unlock-height: new-unlock-height,
lock-duration: new-duration,
last-activity: block-height
})
)
(ok true))
)

(define-public (update-beneficiary (new-beneficiary (optional principal)))
(let (
(vault (unwrap! (get-vault-details tx-sender) ERR-NO-VAULT))
)
(map-set vaults
{ owner: tx-sender }
(merge vault {
beneficiary: new-beneficiary,
beneficiary-status: (if (is-some new-beneficiary) BENEFICIARY-ACTIVE BENEFICIARY-INACTIVE),
last-activity: block-height
})
)
(ok true))
)

(define-public (deposit-stx (amount uint))
(let (
(vault (unwrap! (get-vault-details tx-sender) ERR-NO-VAULT))
)
(try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
(map-set vaults
{ owner: tx-sender }
(merge vault { amount: (+ (get amount vault) amount) })
(merge vault {
amount: (+ (get amount vault) amount),
last-activity: block-height
})
)
(ok true))
)
Expand All @@ -74,10 +167,14 @@
)
(asserts! (>= current-height (get unlock-height vault)) ERR-NOT-UNLOCKED)
(asserts! (<= amount (get amount vault)) ERR-INSUFFICIENT-FUNDS)

(try! (as-contract (stx-transfer? amount (as-contract tx-sender) tx-sender)))
(map-set vaults
{ owner: tx-sender }
(merge vault { amount: (- (get amount vault) amount) })
(merge vault {
amount: (- (get amount vault) amount),
last-activity: block-height
})
)
(ok true))
)
Expand All @@ -87,24 +184,30 @@
(vault (unwrap! (get-vault-details owner) ERR-NO-VAULT))
(current-height block-height)
(grace-end (+ (get unlock-height vault) (get grace-period vault)))
(inactive-period (- current-height (get last-activity vault)))
)
(asserts! (>= current-height grace-end) ERR-NOT-UNLOCKED)
(asserts! (is-some (get beneficiary vault)) ERR-NOT-AUTHORIZED)
;; Verify beneficiary status and conditions
(asserts! (is-some (get beneficiary vault)) ERR-NO-BENEFICIARY)
(asserts! (is-eq (some tx-sender) (get beneficiary vault)) ERR-NOT-AUTHORIZED)
(asserts! (is-eq (get beneficiary-status vault) BENEFICIARY-ACTIVE) ERR-BENEFICIARY-NOT-ACTIVE)
(asserts! (or
(>= current-height grace-end)
(>= inactive-period (get grace-period vault))
) ERR-NOT-UNLOCKED)

;; Transfer funds and close vault
(try! (as-contract (stx-transfer? (get amount vault) (as-contract tx-sender) tx-sender)))
(map-delete vaults { owner: owner })
(ok true))
)

;; Administrative functions
(define-public (update-beneficiary (new-beneficiary (optional principal)))
(define-public (ping)
(let (
(vault (unwrap! (get-vault-details tx-sender) ERR-NO-VAULT))
)
(map-set vaults
{ owner: tx-sender }
(merge vault { beneficiary: new-beneficiary })
(merge vault { last-activity: block-height })
)
(ok true))
)