;; Trajan Protocol Alpha
;; Contract that controls critical Trajan protocol functions (profile registration, organization registration, etc.)
;; Written by Setzeus/StrataLabs
;; Trajan Protocol Alpha
;; This contract is the core Trajan implementation that tracks the registration of profiles & organizations
;; This is currently in Alpha as an incomplete prototype with likely bugs
;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Cons, Vars & Maps ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Temporary Principal Helper
(define-data-var helper-principal principal tx-sender)
;; Watchers
(define-data-var watchers (list 10 principal) (list tx-sender))
;; Organization Counter
(define-data-var organization-counter uint u0)
;; Map of All Registered profiles
;; Profile image URL? Will provie multiples
(define-map profiles principal {
address: principal,
BNS: (buff 20),
BNS-Name: (buff 48),
profile-image-url: (string-ascii 256),
;; To-do -> nft profile pic
Statement: (optional (string-ascii 1024)),
display-profile: bool,
profile-endorsements: (list 2500 uint),
})
;; Memory Math
;; Address -> 34 bytes
;; BNS -> 10 bytes
;; BNS-Name -> 24 bytes
;; Statement -> 1024 bytes
;; display-profile -> 1 byte
;; profile-endorsements -> 10000 bytes
;; Total profile Memory -> 11629 bytes
;; Map of All Registered Organizations
;; What should be the key? uint?
(define-map organization uint {
name: (string-ascii 128),
description: (string-ascii 1024),
admin: principal,
representatives: (list 10 principal),
logo-url: (string-ascii 256),
site-url: (string-ascii 128),
})
;; Organization Memory Math
;; Name -> 128 bytes
;; Description -> 1024 bytes
;; Representatives -> 320 bytes
;; Logo-URL -> 256 bytes
;; Site-URL -> 128 bytes
;; Total Organization Memory -> 1664 bytes
;; Lknow -> Storage costs are part of "execution costs" so the situation is the same
;; Are maps stored in the contract or on-chain separately?
;; If not will need traits for storing deploying storage profile & m
;; Map of All Registered BNS Addresses
;; What is the max string length for a BNS address?
(define-map bns-registered-principal (buff 48) principal)
;; Map of All Organizations Addresses (?)
(define-map organization-addresses (string-ascii 128) uint)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Read-Only Functions ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Get profile By Principal
(define-read-only (get-profile (profile principal))
(map-get? profiles profile)
)
;; Get Organization Represenatives
(define-read-only (get-organization-representatives (org (string-ascii 128)))
(let
(
(checked-org (unwrap! (map-get? organization-addresses org) (err "err-organization-not-found")))
(current-org (unwrap! (map-get? organization checked-org) (err "err-organization-not-found")))
(current-representatives (get representatives current-org))
(current-admin (get admin current-org))
(all-reps (unwrap! (as-max-len? (append current-representatives current-admin) u10) (err "err-representative-overflow")))
)
(ok (get representatives current-org))
)
)
;; Get Organization By ID
(define-read-only (get-organization (org uint))
(map-get? organization org)
)
;; Get Organization ID By Name
(define-read-only (get-organization-id (org (string-ascii 128)))
(map-get? organization-addresses org)
)
;; Get Trajan Watchers
;; @desc - Returns a list of all Trajan Watchers
(define-read-only (get-trajan-watchers)
(var-get watchers)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; profile Functions ;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;
;;; Core Funcs ;;;
;;;;;;;;;;;;;;;;;;
;; Register profile
;; @desc - Registers a new profile
;; @param - BNS-Handle:(string-ascii 128), Statement:(string-ascii 1024)
(define-public (register-profile (bns (buff 20)) (bns-name (buff 48)) (statement (optional (string-ascii 1024))) (profile-image-url (string-ascii 256)))
(let
(
(bns-name-resolve (unwrap! (contract-call? 'SP000000000000000000002Q6VF78.bns name-resolve bns bns-name) (err "err-bns-name-not-found")))
(bns-name-resolve-owner (get owner bns-name-resolve))
(principal-name-resolve (contract-call? 'SP000000000000000000002Q6VF78.bns resolve-principal tx-sender))
(principal-name-resolve-value (get name (unwrap! principal-name-resolve (err "err-unwrapping-principal-name-resolve"))))
(principal-namespace-resolve (get namespace (unwrap! principal-name-resolve (err "err-unwrapping-principal-name-resolve"))))
)
;; Assert BNS not already registered
(asserts! (is-none (map-get? bns-registered-principal bns-name)) (err "err-bns-already-registered"))
;; Assert principal not already registered
(asserts! (is-none (map-get? profiles tx-sender)) (err "err-principal-already-registered"))
;; Assert that principal is tx-sender with both resolves - SP000000000000000000002Q6VF78.bns
(asserts! (and (is-eq tx-sender bns-name-resolve-owner) (is-eq bns principal-namespace-resolve) (is-eq bns-name principal-name-resolve-value)) (err "err-principal-not-bns-owner"))
;; Map-set new profile
(map-set profiles tx-sender {
address: tx-sender,
BNS: bns,
BNS-Name: bns-name,
profile-image-url: profile-image-url,
Statement: statement,
display-profile: true,
profile-endorsements: (list ),
})
;; Map-set new BNS address
(ok (map-set bns-registered-principal bns-name tx-sender))
)
)
;; Remove profile
;; @desc - Allows Owner to remove an existing profile
;; @param - profile:principal
(define-public (remove-profile)
(let
(
(current-profile (unwrap! (map-get? profiles tx-sender) (err "err-profile-not-found")))
(current-profile-principal (get address current-profile))
)
;; Assert that principal is current profile principal
(asserts! (is-eq tx-sender current-profile-principal) (err "err-principal-not-profile-owner"))
;; Map-delete profile -> we won't reset BNS & social links since we don't want anyone else to re-use them
;; Store principal & BNS association in a map for future use (but don't store milestones)
(ok (map-delete profiles tx-sender))
)
)
;;;;;;;;;;;;;;;;;;;;
;;; Update Funcs ;;;
;;;;;;;;;;;;;;;;;;;;
;; Update Profile Image URL
;; @desc - Allows Owner to update an existing profile Profile Image URL
;; @param - Profile-Image-URL:(string-ascii 256)
(define-public (update-profile-image-url (profile-image-url (string-ascii 256)))
(let
(
(current-profile (unwrap! (map-get? profiles tx-sender) (err "err-profile-not-found")))
)
;; Assert that protocol is still intact (aka tx-sender/profile is still owner of BNS they registered with)
(unwrap! (protocol-check-for-corrupted-profile) (err "err-corrupt-profile"))
(ok (map-set profiles tx-sender
(merge
current-profile
{profile-image-url: profile-image-url}
)
))
)
)
;; Update Statement
;; @desc - Allows Owner to update an existing profile Statement
;; @param - Statement:(string-ascii 1024)
(define-public (update-statement (statement (string-ascii 1024)))
(let
(
(current-profile (unwrap! (map-get? profiles tx-sender) (err "err-profile-not-found")))
)
;; Assert that protocol is still intact (aka tx-sender/profile is still owner of BNS they registered with)
(unwrap! (protocol-check-for-corrupted-profile) (err "err-corrupt-profile"))
(ok (map-set profiles tx-sender
(merge
(unwrap! (map-get? profiles tx-sender) (err "err-profile-not-found"))
{Statement: (some statement)}
)
))
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; Organization Functions ;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Add New Representative
;; @desc - Allows Organization Represenatives to add a new representative
;; @param - Representative:principal, Organization:uint
(define-public (add-representative (representative principal) (id uint))
(let
(
(current-organization (unwrap! (map-get? organization id) (err "err-organization-not-found")))
(current-admin (get admin current-organization))
(current-representatives (get representatives current-organization))
)
;; Assert that tx-sender is a representative
(asserts! (or (is-eq tx-sender current-admin) (is-some (index-of current-representatives tx-sender))) (err "err-not-representative"))
;; Map-set organization by merging current-organization with an updated as-max-len? list of representatives
(ok (map-set organization id
(merge
current-organization
{representatives: (unwrap! (as-max-len? (append current-representatives representative) u5) (err "err-representative-limit-reached"))}
)
))
)
)
;; Remove Representative
;; @desc - Allows Organization Represenatives to remove an existing representative
;; @param - Representative:principal, Organization:uint
(define-public (remove-representative (representative principal) (id uint))
(let
(
(current-organization (unwrap! (map-get? organization id) (err "err-organization-not-found")))
(current-admin (get admin current-organization))
(current-representatives (get representatives current-organization))
)
;; Assert that tx-sender is a representative
(asserts! (or (is-eq tx-sender current-admin) (is-some (index-of current-representatives tx-sender))) (err "err-not-representative"))
;; Var-set helper principal
(var-set helper-principal representative)
;; Map-set organization by merging current-organization with an updated as-max-len? list of representatives
(ok (map-set organization id
(merge
current-organization
{representatives: (filter filter-principal current-representatives)}
)
))
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; Watcher Functions ;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Watcher New Organization
;; @desc - Allows Watcher to create a new organization
;; @param - Name:(string-ascii 256), Description:(string-ascii 1024), Website:(string-ascii 256), Logo:(string-ascii 256), Initial-Representative:principal
;; New param (admin-principal ...) should be the STX address that currently owns the .btc name that this organization will be associated with
(define-public (watcher-new-organization (admin-principal principal) (bns (buff 20)) (bns-name (buff 48)) (statement (optional (string-ascii 1024))) (profile-image-url (string-ascii 256)) (name (string-ascii 128)) (description (string-ascii 1024)) (website (string-ascii 128)) (logo (string-ascii 256)) (initial-representatives (list 10 principal)))
(let
(
(current-organization-index (var-get organization-counter))
(next-organization-index (+ current-organization-index u1))
(bns-name-resolve (unwrap! (contract-call? 'SP000000000000000000002Q6VF78.bns name-resolve bns bns-name) (err "err-bns-name-not-found")))
(bns-name-resolve-owner (get owner bns-name-resolve))
(principal-name-resolve (contract-call? 'SP000000000000000000002Q6VF78.bns resolve-principal admin-principal))
(principal-name-resolve-value (get name (unwrap! principal-name-resolve (err "err-unwrapping-principal-name-resolve"))))
(principal-namespace-resolve (get namespace (unwrap! principal-name-resolve (err "err-unwrapping-principal-name-resolve"))))
)
;; Assert that tx-sender is a watcher
(asserts! (is-some (index-of (var-get watchers) tx-sender)) (err "err-not-watcher"))
;; Assert BNS not already registered
(asserts! (is-none (map-get? bns-registered-principal bns-name)) (err "err-bns-already-registered"))
;; Assert principal not already registered
(asserts! (is-none (map-get? profiles admin-principal)) (err "err-principal-already-registered"))
;; Assert that submitted organization-principal resolves with both queries - SP000000000000000000002Q6VF78.bns
(asserts! (and (is-eq admin-principal bns-name-resolve-owner) (is-eq bns principal-namespace-resolve) (is-eq bns-name principal-name-resolve-value)) (err "err-principal-not-bns-owner"))
;; Map-set organization
(map-set organization current-organization-index
{
name: name,
description: description,
admin: admin-principal,
representatives: initial-representatives,
logo-url: logo,
site-url: website,
}
)
;; Map-set organization-addresses
(map-set organization-addresses name current-organization-index)
;; Increment organization-counter
(var-set organization-counter (+ current-organization-index u1))
;; Map-set new profile
(map-set profiles admin-principal {
address: admin-principal,
BNS: bns,
BNS-Name: bns-name,
profile-image-url: profile-image-url,
Statement: statement,
display-profile: true,
profile-endorsements: (list ),
})
;; Map-set new BNS address
(ok (map-set bns-registered-principal bns-name admin-principal))
)
)
;; Watcher Remove profile
;; @desc - Allows Watcher to remove an existing profile
;; @param - profile:principal
(define-public (watcher-remove-profile (profile principal))
(let
(
(current-profile (unwrap! (map-get? profiles profile) (err "err-profile-not-found")))
)
;; Assert that tx-sender is a watcher
(asserts! (is-some (index-of (var-get watchers) tx-sender)) (err "err-not-watcher"))
;; Map-delete profile
(ok (map-delete profiles profile))
)
)
;; Add Watcher
(define-public (add-watcher (new-watcher principal))
(let
(
(new-profile (unwrap! (map-get? profiles new-watcher) (err "err-profile-not-found")))
(current-profile (unwrap! (map-get? profiles tx-sender) (err "err-profile-not-found")))
(current-watchers (var-get watchers))
)
;; Assert that tx-sender is a watcher
(asserts! (is-some (index-of current-watchers tx-sender)) (err "err-not-watcher"))
;; Assert that new-watcher is not already a watcher
(asserts! (not (is-some (index-of current-watchers new-watcher))) (err "err-watcher-already-exists"))
;; Var-set new watch by appending to list as-max-len?
(ok (var-set watchers (unwrap! (as-max-len? (append current-watchers new-watcher) u5) (err "err-watcher-limit-reached"))))
)
)
;; Remove Watcher
(define-public (remove-watcher (watcher principal))
(let
(
(removing-profile (unwrap! (map-get? profiles watcher) (err "err-profile-not-found")))
(current-profile (unwrap! (map-get? profiles tx-sender) (err "err-profile-not-found")))
(current-watchers (var-get watchers))
)
;; Assert that tx-sender is a watcher
(asserts! (is-some (index-of current-watchers tx-sender)) (err "err-not-watcher"))
;; Asset that remove-profile is a watcher
(asserts! (is-some (index-of current-watchers watcher)) (err "err-not-watcher"))
;; Var-set helper principal
(var-set helper-principal watcher)
;; Filter to remove out princpal from watcher list
(ok (var-set watchers (filter filter-principal current-watchers)))
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Helper Functions ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-public (protocol-check-for-corrupted-profile)
(let
(
(current-profile (unwrap! (map-get? profiles tx-sender) (err "err-profile-not-found")))
(current-profile-bns (get BNS current-profile))
(current-profile-bns-name (get BNS-Name current-profile))
(current-principal-name-resolve (contract-call? 'SP000000000000000000002Q6VF78.bns resolve-principal tx-sender))
(principal-name-resolve-value (get name (unwrap! current-principal-name-resolve (err "err-principal-name-resolve-failed"))))
(principal-namespace-resolve (get namespace (unwrap! current-principal-name-resolve (err "err-principal-namespace-resolve-failed"))))
(bns-name-resolve (unwrap! (contract-call? 'SP000000000000000000002Q6VF78.bns name-resolve principal-namespace-resolve principal-name-resolve-value) (err "err-bns-name-resolve-failed")))
(bns-name-resolve-owner (get owner bns-name-resolve))
)
;; Check if principal is tx-sender with both resolves - SP000000000000000000002Q6VF78.bns
(ok (if (and (is-eq tx-sender bns-name-resolve-owner) (is-eq current-profile-bns principal-namespace-resolve) (is-eq current-profile-bns-name principal-name-resolve-value))
;; Principal tied to tx-sender
true
;; Principal not tied to tx-sender, remove profile
(begin
(map-delete profiles tx-sender)
false
)
))
)
)
;; Private helper function for filtering/removing a principal (principal-helper)
(define-private (filter-principal (item principal))
(not (is-eq item (var-get helper-principal)))
)
;; Helper Function To Convert ASCII to Buffer
(define-read-only (asci2buff (in (string-ascii 100)))
(fold ascii2buff_clojure in 0x)
)
(define-private (ascii2buff_clojure (chr (string-ascii 1)) (out (buff 100)))
(match (index-of " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" chr) idx
(match (element-at 0x202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E idx) x
(unwrap-panic (as-max-len? (concat out x) u100))
out
)
out
)
)
;; Helper Function To Convert Buffer to ASCII
(define-read-only (buff2ascii (in (buff 100)))
(fold buff2ascii_clojure in "")
)
(define-private (buff2ascii_clojure (buff (buff 1)) (out (string-ascii 100)))
(match (index-of 0x202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E buff) idx
(match (element-at " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" idx) x
(unwrap-panic (as-max-len? (concat out x) u100))
out
)
out
)
)