;; A multi-signature wallet

(use-trait executor-trait .traits.executor-trait)
(use-trait wallet-trait .traits.wallet-trait)

(impl-trait .traits.wallet-trait)

;; errors
(define-constant ERR-CALLER-MUST-BE-SELF (err u100))
(define-constant ERR-OWNER-ALREADY-EXISTS (err u110))
(define-constant ERR-OWNER-NOT-EXISTS (err u120))
(define-constant ERR-UNAUTHORIZED-SENDER (err u130))
(define-constant ERR-TX-NOT-FOUND (err u140))
(define-constant ERR-TX-ALREADY-CONFIRMED-BY-OWNER (err u150))
(define-constant ERR-TX-INVALID-EXECUTOR (err u160))
(define-constant ERR-INVALID-WALLET (err u170))
(define-constant ERR-TX-CONFIRMED (err u180))
(define-constant ERR-TX-NOT-CONFIRMED-BY-SENDER (err u190))
(define-constant ERR-CALLER-MUST-BE-ADMIN (err u200))
(define-constant ERR-INVALID-RECEIVER (err u210))
(define-constant ERR-INVALID-RATE (err u220))

;; version
(define-constant VERSION "0.0.1.alpha")

(define-read-only (get-version)
    VERSION
)

;; principal of the deployed contract
(define-constant self (as-contract tx-sender))


;; owners
(define-data-var owners (list 50 principal) (list))

(define-read-only (get-owners)
    (var-get owners)
)

(define-read-only (is-validator (validator principal))
    (is-some (index-of (var-get owners) validator))
)

(define-private (add-owner-internal (owner principal))
    (var-set owners (unwrap-panic (as-max-len? (append (var-get owners) owner) u50)))
)

(define-public (add-owner (owner principal))
    (begin
        (asserts! (is-eq tx-sender self) ERR-CALLER-MUST-BE-SELF)
        (asserts! (is-none (index-of (var-get owners) owner)) ERR-OWNER-ALREADY-EXISTS)
        (ok (add-owner-internal owner))
    )
)

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

(define-private (remove-owner-filter (o principal)) (not (is-eq o (var-get rem-owner))))

(define-public (remove-owner (owner principal))
    (begin
        (asserts! (is-eq tx-sender self) ERR-CALLER-MUST-BE-SELF)
        (asserts! (not ( is-none (index-of (var-get owners) owner) )) ERR-OWNER-NOT-EXISTS)
        (var-set rem-owner owner)
        (ok (var-set owners (unwrap-panic (as-max-len? (filter remove-owner-filter (var-get owners)) u50))))
    )
)


;; minimum confirmation requirement
(define-data-var min-confirmation uint u0)

(define-read-only (get-min-confirmation)
    (var-get min-confirmation)
)

(define-private (set-min-confirmation-internal (value uint))
    (var-set min-confirmation value)
)

(define-public (set-min-confirmation (value uint))
    (begin
        (asserts! (is-eq tx-sender self) ERR-CALLER-MUST-BE-SELF)
        (ok (set-min-confirmation-internal value))
    )
)

;; nonce
(define-data-var nonce uint u0)

(define-read-only (get-nonce)
    (var-get nonce)
)

(define-private (increase-nonce)
    (var-set nonce (+ (var-get nonce) u1))
)

;; transactions
(define-map transactions
    uint
    {
        executor: principal,
        confirmations: (list 50 principal),
        confirmed: bool,
        arg-p: principal,
        arg-u: uint,
        arg-buff: (buff 256),
        arg-bool: bool
    }
)

(define-private (add (executor <executor-trait>) (arg-p principal) (arg-u uint) (arg-buff (buff 256)) (arg-bool bool))
    (let
        (
            (tx-id (get-nonce))
        )
        (map-insert transactions tx-id {executor: (contract-of executor), confirmations: (list), confirmed: false, arg-p: arg-p, arg-u: arg-u, arg-buff: arg-buff, arg-bool: arg-bool})
        (increase-nonce)
        tx-id
    )
)

(define-read-only (get-transaction (tx-id uint))
    (unwrap-panic (map-get? transactions tx-id))
)

(define-read-only (get-transactions (tx-ids (list 20 uint)))
    (map get-transaction tx-ids)
)

(define-data-var rem-confirmation principal tx-sender)

(define-private (remove-confirmation-filter (o principal)) (not (is-eq o (var-get rem-confirmation))))

(define-public (revoke (tx-id uint))
    (let
        (
            (tx (unwrap! (map-get? transactions tx-id) ERR-TX-NOT-FOUND))
            (confirmations (get confirmations tx))
        )
        (asserts! (is-eq (get confirmed tx) false) ERR-TX-CONFIRMED)
        (asserts! (is-some (index-of confirmations contract-caller)) ERR-TX-NOT-CONFIRMED-BY-SENDER)
        (var-set rem-confirmation contract-caller)
        (let
            (
                (new-confirmations  (unwrap-panic (as-max-len? (filter remove-confirmation-filter confirmations) u50)))
                (new-tx (merge tx {confirmations: new-confirmations}))
            )
            (map-set transactions tx-id new-tx)
            (ok true)
        )
    )
)

(define-public (confirm (tx-id uint) (executor <executor-trait>) (wallet <wallet-trait>))
    (begin
        (asserts! (is-some (index-of (var-get owners) contract-caller)) ERR-UNAUTHORIZED-SENDER)
        (asserts! (is-eq (contract-of wallet) self) ERR-INVALID-WALLET)
        (let
            (
                (tx (unwrap! (map-get? transactions tx-id) ERR-TX-NOT-FOUND))
                (confirmations (get confirmations tx))
            )

            (asserts! (is-none (index-of confirmations contract-caller)) ERR-TX-ALREADY-CONFIRMED-BY-OWNER)
            (asserts! (is-eq (get executor tx) (contract-of executor)) ERR-TX-INVALID-EXECUTOR)

            (let
                (
                    (new-confirmations (unwrap-panic (as-max-len? (append confirmations contract-caller) u50)))
                    (confirmed (>= (len new-confirmations) (var-get min-confirmation)))
                    (new-tx (merge tx {confirmations: new-confirmations, confirmed: confirmed}))
                )
                (map-set transactions tx-id new-tx)
                (if confirmed
                    (try! (as-contract (contract-call? executor execute wallet (get arg-p tx) (get arg-u tx) (get arg-buff tx) (get arg-bool tx))))
                    false
                )
                (ok confirmed)
            )
        )
    )
)

(define-public (submit (executor <executor-trait>) (wallet <wallet-trait>) (arg-p principal) (arg-u uint) (arg-buff (buff 256)) (arg-bool bool))
    (begin
        (asserts! (is-some (index-of (var-get owners) contract-caller)) ERR-UNAUTHORIZED-SENDER)
        (asserts! (is-eq (contract-of wallet) self) ERR-INVALID-WALLET)
        (let
            ((tx-id (add executor arg-p arg-u arg-buff arg-bool)))
            (unwrap-panic (confirm tx-id executor wallet))
            (ok tx-id)
        )
    )
)

;; bridge
(define-map valid-chain (buff 256) bool)
(define-map chain-fee (buff 256) uint)
(define-map chain-fee-with-data (buff 256) uint)
(define-data-var fee-governance principal tx-sender)
(define-data-var tax-receiver (buff 20) 0xe9f3604b85c9672728eeecf689cf1f0cf7dd03f2)
(define-data-var tax-rate uint u10)
(define-data-var policy-admin principal tx-sender)

(define-read-only (is-valid-chain (chain (buff 256)))
    (unwrap! (map-get? valid-chain chain) false)
)

(define-read-only (get-fee-governance)
    (var-get fee-governance)
)

(define-read-only (get-chain-fee (chain (buff 256)))
    (unwrap! (map-get? chain-fee chain) u0)
)

(define-read-only (get-chain-fee-with-data (chain (buff 256)))
    (unwrap! (map-get? chain-fee-with-data chain) u0)
)

(define-read-only (get-tax-receiver)
    (var-get tax-receiver)
)

(define-read-only (get-tax-rate)
    (var-get tax-rate)
)

(define-read-only (get-policy-admin)
    (var-get policy-admin)
)

(define-private (set-valid-chain-internal (chain (buff 256)) (valid bool))
    (map-set valid-chain chain valid)
)

(define-public (set-valid-chain (chain (buff 256)) (valid bool))
    (begin
        (asserts! (is-eq tx-sender self) ERR-CALLER-MUST-BE-SELF)
        (ok (set-valid-chain-internal chain valid))
    )
)

(define-private (set-policy-admin-internal (admin principal))
    (var-set policy-admin admin)
)

(define-public (set-policy-admin (admin principal))
    (begin
        (asserts! (is-eq tx-sender self) ERR-CALLER-MUST-BE-SELF)
        (ok (set-policy-admin-internal admin))
    )
)

(define-private (set-tax-config-internal (receiver (buff 20)) (rate uint))
    (begin
        (var-set tax-receiver receiver)
        (var-set tax-rate rate)
    )
)

(define-public (set-tax-config (receiver (buff 20)) (rate uint))
    (begin
        (asserts! (is-eq tx-sender self) ERR-CALLER-MUST-BE-SELF)
        (asserts! (is-eq (len receiver) u20) ERR-INVALID-RECEIVER)
        (asserts! (is-eq (< rate u10000) true) ERR-INVALID-RATE)
        (ok (set-tax-config-internal receiver rate))
    )
)

(define-private (set-fee-governance-internal (receiver principal))
    (var-set fee-governance receiver)
)

(define-public (set-fee-governance (receiver principal))
    (begin
        (asserts! (is-eq tx-sender self) ERR-CALLER-MUST-BE-SELF)
        (ok (set-fee-governance-internal receiver))
    )
)

(define-public (set-chain-fee (chain (buff 256)) (fee uint) (fee-with-data uint))
    (begin
        (asserts! (is-eq tx-sender (var-get policy-admin)) ERR-CALLER-MUST-BE-ADMIN)
        (map-set chain-fee chain fee)
        (ok (map-set chain-fee-with-data chain fee-with-data))
    )
)

;; init
(define-private (init (o (list 50 principal)) (m uint))
    (begin
        (map add-owner-internal o)
        (set-min-confirmation-internal m)
    )
)

(init (list
    'SP75M7424Q2WA7PRG140J63PJM1EVTZ5HGRA3MNG ;; Ozys
    'ST75M7424Q2WA7PRG140J63PJM1EVTZ5HJNNYQJW
) u1)

(set-valid-chain-internal 0x4b4c4159544e true) ;; KLAYTN
(set-valid-chain-internal 0x4f52424954 true) ;; ORBIT
(set-valid-chain-internal 0x41564158 true) ;; AVAX
(set-valid-chain-internal 0x425343 true) ;; BSC
(set-valid-chain-internal 0x43454c4f true) ;; CELO
(set-valid-chain-internal 0x455448 true) ;; ETH
(set-valid-chain-internal 0x46414e544f4d true) ;; FANTOM
(set-valid-chain-internal 0x4845434f true) ;; HECO
(set-valid-chain-internal 0x4d41544943 true) ;; MATIC
(set-valid-chain-internal 0x4d4f4f4e5249564552 true) ;; MOONRIVER
(set-valid-chain-internal 0x4f4543 true) ;; OEC
(set-valid-chain-internal 0x58444149 true) ;; XDAI
(set-valid-chain-internal 0x544f4e true) ;; TON
(set-valid-chain-internal 0x4d4554414449554d true) ;; METADIUM
(set-valid-chain-internal 0x57454d4958 true) ;; WEMIX
