initial commit

This commit is contained in:
fiftyeightandeight
2024-02-08 12:07:58 +08:00
commit 8f3d1347d5
8 changed files with 1533 additions and 0 deletions

98
LICENSE Normal file
View File

@@ -0,0 +1,98 @@
Business Source License 1.1
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
"Business Source License" is a trademark of MariaDB Corporation Ab.
-----------------------------------------------------------------------------
Parameters
Licensor: ALEXGO Pte. Ltd.
Licensed Work: alex-v2-orderbook
The Licensed Work is (c) 2022-2024 ALEXGO Pte. Ltd.
Additional Use Grant: None
Change Date: 2028-01-01
Change License: MIT License
-----------------------------------------------------------------------------
Terms
The Licensor hereby grants you the right to copy, modify, create derivative
works, redistribute, and make non-production use of the Licensed Work. The
Licensor may make an Additional Use Grant, above, permitting limited
production use.
Effective on the Change Date, or the fourth anniversary of the first publicly
available distribution of a specific version of the Licensed Work under this
License, whichever comes first, the Licensor hereby grants you rights under
the terms of the Change License, and the rights granted in the paragraph
above terminate.
If your use of the Licensed Work does not comply with the requirements
currently in effect as described in this License, you must purchase a
commercial license from the Licensor, its affiliated entities, or authorized
resellers, or you must refrain from using the Licensed Work.
All copies of the original and modified Licensed Work, and derivative works
of the Licensed Work, are subject to this License. This License applies
separately for each version of the Licensed Work and the Change Date may vary
for each version of the Licensed Work released by Licensor.
You must conspicuously display this License on each original or modified copy
of the Licensed Work. If you receive the Licensed Work in original or
modified form from a third party, the terms and conditions set forth in this
License apply to your use of that work.
Any use of the Licensed Work in violation of this License will automatically
terminate your rights under this License for the current and all other
versions of the Licensed Work.
This License does not grant you any right in any trademark or logo of
Licensor or its affiliates (provided that you may use a trademark or logo of
Licensor as expressly required by this License).
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
TITLE.
MariaDB hereby grants you permission to use this Licenses text to license
your works, and to refer to it using the trademark "Business Source License",
as long as you comply with the Covenants of Licensor below.
-----------------------------------------------------------------------------
Covenants of Licensor
In consideration of the right to use this Licenses text and the "Business
Source License" name and trademark, Licensor covenants to MariaDB, and to all
other recipients of the licensed work to be provided by Licensor:
1. To specify as the Change License the GPL Version 2.0 or any later version,
or a license that is compatible with GPL Version 2.0 or a later version,
where "compatible" means that software provided under the Change License can
be included in a program with software provided under GPL Version 2.0 or a
later version. Licensor may specify additional Change Licenses without
limitation.
2. To either: (a) specify an additional grant of rights to use that does not
impose any additional restriction on the right granted in this License, as
the Additional Use Grant; or (b) insert the text "None".
3. To specify a Change Date.
4. Not to modify this License in any other way.
-----------------------------------------------------------------------------
Notice
The Business Source License (this document, or the "License") is not an Open
Source license. However, the Licensed Work will eventually be made available
under an Open Source License, as stated in this License.

50
README.md Normal file
View File

@@ -0,0 +1,50 @@
# STXDX Contracts
This project contains several Clarity smart contracts that enable decentralized trading of fungible and non-fungible tokens on the Stacks blockchain.
## Overview
The core components are:
- `stxdx-exchange-zero` - Implements the decentralized exchange functionality for trading tokens
- `stxdx-wallet-zero` - Holds user balances and processes deposits/withdrawals
- `stxdx-registry` - Maps user IDs and asset IDs to their respective principals
- `stxdx-sender-proxy` - Allows authorized users to submit transactions to the exchange
- `redstone-verify` - Library for verifying RedStone signatures
Together, these contracts allow users to trustlessly trade tokens in a decentralized manner, without requiring a centralized exchange operator. The registry provides efficient lookup of principals, the wallet holds user balances, the exchange facilitates trading, and the proxy allows delegated access.
## Contract Details
### stxdx-exchange-zero.clar
This contract implements the core exchange logic. Key functionality includes:
- Placing limit orders for trades
- Filling existing orders by matching compatible trades
- Getting status of open orders
- Cancelling unfilled orders
- Setting oracle price feeds for assets
- Orders are specified in terms of user IDs and asset IDs, which are resolved to principals via the registry.
### stxdx-wallet-zero.clar
The wallet holds balances of assets for different user accounts. It enables:
- Depositing asset tokens into the wallet
- Requesting withdrawals from the wallet
- Approving pending withdrawal requests
- Transferring balances between user accounts
- The wallet integrates with the registry to lookup user and asset principals. Withdrawals follow a 2-step process to provide security.
### stxdx-registry.clar
This simple registry contract maps user IDs and asset IDs to their respective principals. It allows efficient lookup of principals by ID instead of passing around full principals in transaction data.
### stxdx-sender-proxy.clar
This proxy contract allows authorized users to submit transactions to the exchange on behalf of other accounts. This provides a permissioned layer for delegated trading.
### redstone-verify.clar
General purpose signature verification library for validating RedStone signed payloads. Used by other contracts for verifying signatures from oracle price feeds.
## Usage
The main entrypoints are the exchange and wallet contracts. Users can trade tokens on the decentralized exchange via `stxdx-exchange-zero`, and deposit/withdraw via `stxdx-wallet-zero`.
The sender proxy allows delegated trading, while the registry is used internally for efficient data storage.
## License
This project is licensed under the BUSL license - see the LICENSE file for details.

107
redstone-verify.clar Normal file
View File

@@ -0,0 +1,107 @@
;; RedStone Clarity Connector project.
;; The redstone-verify contract is a stateless library contract that other
;; contracts can call into to verify RedStone messages.
;; By Marvin Janssen
(define-constant eth-personal-sign-prefix 0x19457468657265756D205369676E6564204D6573736167653A0A3332)
(define-constant redstone-value-shift u100000000)
;; Timestamp is ceiled, so we need this structure.
(define-read-only (shift-timestamp (timestamp uint))
(if (> (mod timestamp u1000) u0)
(+ (/ timestamp u1000) u1)
(/ timestamp u1000)
)
)
(define-read-only (get-redstone-value-shift)
redstone-value-shift
)
(define-private (assemble-iter (entry {symbol: (buff 32), value: uint}) (a (buff 640)))
(unwrap-panic (as-max-len? (concat a (concat (right-pad32 (get symbol entry)) (uint256-to-buff-be (get value entry)))) u640))
)
(define-read-only (generate-signable-message-hash (timestamp uint) (entries (list 10 {symbol: (buff 32), value: uint})))
(keccak256
(concat
eth-personal-sign-prefix
(keccak256 (concat (fold assemble-iter entries 0x) (uint256-to-buff-be (shift-timestamp timestamp))))
)
)
)
(define-read-only (generate-lite-data-bytes (timestamp uint) (entries (list 10 {symbol: (buff 32), value: uint})))
(concat (fold assemble-iter entries 0x) (uint256-to-buff-be (shift-timestamp timestamp)))
)
(define-read-only (verify-message (timestamp uint) (entries (list 10 {symbol: (buff 32), value: uint})) (signature (buff 65)) (public-key (buff 33)))
(secp256k1-verify (generate-signable-message-hash timestamp entries) signature public-key)
)
(define-read-only (recover-signer (timestamp uint) (entries (list 10 {symbol: (buff 32), value: uint})) (signature (buff 65)))
(secp256k1-recover? (generate-signable-message-hash timestamp entries) signature)
)
(define-private (recover-signer-hash (hash (buff 32)) (signature (buff 65)))
(secp256k1-recover? hash signature)
)
(define-read-only (recover-signer-multi (timestamp uint) (entries (list 10 {symbol: (buff 32), value: uint})) (signatures (list 8 (buff 65))))
(let ((hash (generate-signable-message-hash timestamp entries)))
(map recover-signer-hash (list hash hash hash hash hash hash hash hash) signatures)
)
)
(define-constant byte-list 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff)
(define-private (uint-to-buff-iter (b (buff 1)) (p {n: uint, a: (buff 32)}))
{
a: (unwrap-panic (as-max-len? (concat (if (is-eq (get n p) u0) 0x00 (unwrap-panic (element-at byte-list (mod (get n p) u256)))) (get a p)) u32)),
n: (/ (get n p) u256)
}
)
(define-read-only (uint256-to-buff-be (n uint))
(unwrap-panic (as-max-len? (get a (fold uint-to-buff-iter 0x0000000000000000000000000000000000000000000000000000000000000000 {n: n, a: 0x})) u32))
)
(define-constant pad32-padding (list
0x0000000000000000000000000000000000000000000000000000000000000000
0x00000000000000000000000000000000000000000000000000000000000000
0x000000000000000000000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000000000000000000000
0x00000000000000000000000000000000000000000000000000000000
0x000000000000000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000000000000000
0x00000000000000000000000000000000000000000000000000
0x000000000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000000000
0x00000000000000000000000000000000000000000000
0x000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000
0x00000000000000000000000000000000000000
0x000000000000000000000000000000000000
0x0000000000000000000000000000000000
0x00000000000000000000000000000000
0x000000000000000000000000000000
0x0000000000000000000000000000
0x00000000000000000000000000
0x000000000000000000000000
0x0000000000000000000000
0x00000000000000000000
0x000000000000000000
0x0000000000000000
0x00000000000000
0x000000000000
0x0000000000
0x00000000
0x000000
0x0000
0x00
0x
))
(define-read-only (right-pad32 (b (buff 32)))
(concat b (unwrap-panic (element-at pad32-padding (len b))))
)

697
stxdx-exchange-zero.clar Normal file
View File

@@ -0,0 +1,697 @@
(impl-trait .trait-ownable.ownable-trait)
;; 3000-3999: core errors
(define-constant err-unauthorised-sender (err u3000))
(define-constant err-maker-asset-mismatch (err u3001))
(define-constant err-taker-asset-mismatch (err u3002))
(define-constant err-asset-data-mismatch (err u3003))
(define-constant err-left-order-expired (err u3005))
(define-constant err-right-order-expired (err u3006))
(define-constant err-left-authorisation-failed (err u3007))
(define-constant err-right-authorisation-failed (err u3008))
(define-constant err-maximum-fill-reached (err u3009))
(define-constant err-maker-not-tx-sender (err u3010))
(define-constant err-invalid-timestamp (err u3011))
(define-constant err-unknown-asset-id (err u3501))
;; 4000-4999: registry errors
(define-constant err-unauthorised-caller (err u4000))
;; 5000-5999: exchange errors
(define-constant err-asset-data-too-long (err u5003))
(define-constant err-sender-fee-payment-failed (err u5007))
(define-constant err-asset-contract-call-failed (err u5008))
(define-constant err-stop-not-triggered (err u5009))
(define-constant err-invalid-order-type (err u5010))
(define-constant err-cancel-authorisation-failed (err u5011))
(define-constant err-paused (err u5012))
(define-constant err-left-sender-fee (err u5013))
(define-constant err-right-sender-fee (err u5014))
;; 6000-6999: oracle errors
(define-constant err-untrusted-oracle (err u6000))
(define-constant err-no-oracle-data (err u6001))
(define-constant structured-data-prefix 0x534950303138)
;; tupleCV({
;; name: stringAsciiCV('ALEX B20 Protocol'),
;; version: stringAsciiCV('0.0.1'),
;; 'chain-id': uintCV(new StacksMainnet().chainId),
;; }),
(define-constant message-domain-mainnet 0xa2a2221ace8a76ed4729b2838098dd80712796258c80e454c818590c2e26333f)
;; 'chain-id': uintCV(new StacksMocknet().chainId),
(define-constant message-domain-testnet 0xced498e8ba3e44d9752ebdd05c2a064cadc411fad5ff1b1d5204857f105f495b)
(define-constant type-order-vanilla u0)
(define-constant type-order-fok u1)
(define-constant type-order-ioc u2)
(define-constant ONE_8 u100000000)
(define-constant MAX_UINT u340282366920938463463374607431768211455)
(define-data-var contract-owner principal tx-sender)
(define-data-var fee-address principal tx-sender)
(define-data-var is-paused bool false)
(define-map authorised-senders principal bool)
(define-map trusted-oracles (buff 33) bool)
(define-map oracle-symbols uint (buff 32))
(define-map triggered-orders (buff 32) { triggered: bool, timestamp: uint })
(define-data-var default-maker-fee uint u1000000)
(define-data-var default-taker-fee uint u1000000)
(define-map user-fee uint { maker-fee: uint, taker-fee: uint })
;; read-only calls
(define-read-only (message-domain)
(if (is-eq chain-id u1)
message-domain-mainnet
message-domain-testnet
)
)
(define-read-only (get-default-maker-fee)
(var-get default-maker-fee)
)
(define-read-only (get-default-taker-fee)
(var-get default-taker-fee)
)
(define-read-only (get-maker-fee (user-id uint))
(match (map-get? user-fee user-id)
fee
(get maker-fee fee)
(var-get default-maker-fee)
)
)
(define-read-only (get-taker-fee (user-id uint))
(match (map-get? user-fee user-id)
fee
(get taker-fee fee)
(var-get default-taker-fee)
)
)
(define-read-only (get-maker-fee-by-address (user principal))
(match (contract-call? .stxdx-registry get-user-id user)
user-id
(get-maker-fee user-id)
(var-get default-maker-fee)
)
)
(define-read-only (get-taker-fee-by-address (user principal))
(match (contract-call? .stxdx-registry get-user-id user)
user-id
(get-taker-fee user-id)
(var-get default-taker-fee)
)
)
(define-read-only (get-paused)
(var-get is-paused)
)
(define-read-only (is-trusted-oracle (pubkey (buff 33)))
(default-to false (map-get? trusted-oracles pubkey))
)
(define-read-only (get-oracle-symbol-or-fail (asset-id uint))
(ok (unwrap! (map-get? oracle-symbols asset-id) err-unknown-asset-id))
)
(define-read-only (is-order-triggered (order-hash (buff 32)))
(match (map-get? triggered-orders order-hash)
value
(get triggered value)
false
)
)
(define-read-only (get-triggered-orders-or-default (order-hash (buff 32)))
(default-to { triggered: false, timestamp: MAX_UINT } (map-get? triggered-orders order-hash))
)
(define-read-only (hash-cancel-order (order-hash (buff 32)))
(sha256 (default-to 0x (to-consensus-buff? { hash: order-hash, cancel: true })))
)
(define-read-only (get-contract-owner)
(ok (var-get contract-owner))
)
(define-read-only (get-fee-address)
(var-get fee-address)
)
(define-read-only (hash-order
(order
{
sender: uint,
sender-fee: uint,
maker: uint,
maker-asset: uint,
taker-asset: uint,
maker-asset-data: uint,
taker-asset-data: uint,
maximum-fill: uint,
expiration-height: uint,
salt: uint,
risk: bool,
stop: uint,
timestamp: uint,
type: uint
}
)
)
(sha256 (default-to 0x (to-consensus-buff? order)))
)
(define-read-only (validate-match
(left-order
{
sender: uint,
sender-fee: uint,
maker: uint,
maker-asset: uint,
taker-asset: uint,
maker-asset-data: uint,
taker-asset-data: uint,
maximum-fill: uint,
expiration-height: uint,
salt: uint,
risk: bool,
stop: uint,
timestamp: uint,
type: uint
}
)
(right-order
{
sender: uint,
sender-fee: uint,
maker: uint,
maker-asset: uint,
taker-asset: uint,
maker-asset-data: uint,
taker-asset-data: uint,
maximum-fill: uint,
expiration-height: uint,
salt: uint,
risk: bool,
stop: uint,
timestamp: uint,
type: uint
}
)
(left-signature (buff 65))
(right-signature (buff 65))
(left-oracle-data (optional { timestamp: uint, value: uint, signature: (buff 65) }))
(right-oracle-data (optional { timestamp: uint, value: uint, signature: (buff 65) }))
(fill (optional uint))
)
(let
(
(users (try! (contract-call? .stxdx-registry get-two-users-from-id-or-fail (get maker left-order) (get maker right-order))))
(left-order-hash (hash-order left-order))
(right-order-hash (hash-order right-order))
(order-fills (contract-call? .stxdx-registry get-two-order-fills left-order-hash right-order-hash))
(left-order-fill (get order-1 order-fills))
(right-order-fill (get order-2 order-fills))
(fillable (min (- (get maximum-fill left-order) left-order-fill) (- (get maximum-fill right-order) right-order-fill)))
(left-sender-fee (get-maker-fee (get maker left-order)))
(right-sender-fee (get-taker-fee (get maker right-order)))
)
(try! (is-authorised-sender))
;; there are more fills to do
(match fill value (asserts! (>= fillable value) err-maximum-fill-reached) (asserts! (> fillable u0) err-maximum-fill-reached))
;; both orders are not expired
(asserts! (< block-height (get expiration-height left-order)) err-left-order-expired)
(asserts! (< block-height (get expiration-height right-order)) err-right-order-expired)
;; assets to be exchanged match
(asserts! (is-eq (get maker-asset left-order) (get taker-asset right-order)) err-maker-asset-mismatch)
(asserts! (is-eq (get taker-asset left-order) (get maker-asset right-order)) err-taker-asset-mismatch)
;; asserts fee locked >= fee to be paid
(asserts! (>= (get sender-fee left-order) left-sender-fee) err-left-sender-fee)
(asserts! (>= (get sender-fee right-order) right-sender-fee) err-right-sender-fee)
;; one side matches and the taker of the other side is smaller than maker.
;; so that maker gives at most maker-asset-data, and taker takes at least taker-asset-data
(asserts!
(or
(and
(is-eq (get maker-asset-data left-order) (get taker-asset-data right-order))
(<= (get taker-asset-data left-order) (get maker-asset-data right-order))
)
(and
(is-eq (get taker-asset-data left-order) (get maker-asset-data right-order))
(>= (get maker-asset-data left-order) (get taker-asset-data right-order))
)
)
err-asset-data-mismatch
)
;; stop limit order
(if (and (or (is-order-triggered left-order-hash) (is-eq (get stop left-order) u0)) (or (is-order-triggered right-order-hash) (is-eq (get stop right-order) u0)))
(asserts!
(<=
(if (is-order-triggered left-order-hash)
(get timestamp (get-triggered-orders-or-default left-order-hash))
(get timestamp left-order)
)
(if (is-order-triggered right-order-hash)
(get timestamp (get-triggered-orders-or-default right-order-hash))
(get timestamp right-order)
)
)
err-invalid-timestamp
) ;; left-order must be older than right-order
(if (and (or (is-order-triggered left-order-hash) (is-eq (get stop left-order) u0)) (is-some right-oracle-data))
(let
(
(oracle-data (unwrap! right-oracle-data err-no-oracle-data))
(is-buy (is-some (map-get? oracle-symbols (get taker-asset right-order))))
(symbol (try! (get-oracle-symbol-or-fail (if is-buy (get taker-asset right-order) (get maker-asset right-order)))))
(signer (try! (contract-call? .redstone-verify recover-signer (get timestamp oracle-data) (list {value: (get value oracle-data), symbol: symbol}) (get signature oracle-data))))
)
(asserts! (is-trusted-oracle signer) err-untrusted-oracle)
(asserts! (<= (get timestamp right-order) (get timestamp oracle-data)) err-invalid-timestamp)
(asserts!
(<=
(if (is-order-triggered left-order-hash)
(get timestamp (get-triggered-orders-or-default left-order-hash))
(get timestamp left-order)
)
(get timestamp oracle-data)
)
err-invalid-timestamp
)
(if (get risk right-order) ;; it is risk-mgmt stop limit, i.e. buy on the way up (to hedge sell) or sell on the way down (to hedge buy)
(asserts! (if is-buy (>= (get value oracle-data) (get stop right-order)) (<= (get value oracle-data) (get stop right-order))) err-stop-not-triggered)
(asserts! (if is-buy (<= (get value oracle-data) (get stop right-order)) (>= (get value oracle-data) (get stop right-order))) err-stop-not-triggered)
)
)
(if (and (is-some left-oracle-data) (or (is-order-triggered right-order-hash) (is-eq (get stop right-order) u0)))
(let
(
(oracle-data (unwrap! left-oracle-data err-no-oracle-data))
(is-buy (is-some (map-get? oracle-symbols (get taker-asset left-order))))
(symbol (try! (get-oracle-symbol-or-fail (if is-buy (get taker-asset left-order) (get maker-asset left-order)))))
(signer (try! (contract-call? .redstone-verify recover-signer (get timestamp oracle-data) (list {value: (get value oracle-data), symbol: symbol}) (get signature oracle-data))))
)
(asserts! (is-trusted-oracle signer) err-untrusted-oracle)
(asserts! (<= (get timestamp left-order) (get timestamp oracle-data)) err-invalid-timestamp)
(asserts!
(<=
(get timestamp oracle-data)
(if (is-order-triggered right-order-hash)
(get timestamp (get-triggered-orders-or-default right-order-hash))
(get timestamp right-order)
)
)
err-invalid-timestamp
)
(if (get risk left-order) ;; it is risk-mgmt stop limit, i.e. buy on the way up (to hedge sell) or sell on the way down (to hedge buy)
(asserts! (if is-buy (>= (get value oracle-data) (get stop left-order)) (<= (get value oracle-data) (get stop left-order))) err-stop-not-triggered)
(asserts! (if is-buy (<= (get value oracle-data) (get stop left-order)) (>= (get value oracle-data) (get stop left-order))) err-stop-not-triggered)
)
)
(let
(
(left-data (unwrap! left-oracle-data err-no-oracle-data))
(left-buy (is-some (map-get? oracle-symbols (get taker-asset left-order))))
(symbol (try! (get-oracle-symbol-or-fail (if left-buy (get taker-asset left-order) (get maker-asset left-order)))))
(left-signer (try! (contract-call? .redstone-verify recover-signer (get timestamp left-data) (list {value: (get value left-data), symbol: symbol}) (get signature left-data))))
(right-data (unwrap! right-oracle-data err-no-oracle-data))
(right-buy (not left-buy))
(right-signer (try! (contract-call? .redstone-verify recover-signer (get timestamp right-data) (list {value: (get value right-data), symbol: symbol}) (get signature right-data))))
)
(asserts! (and (is-trusted-oracle left-signer) (is-trusted-oracle right-signer)) err-untrusted-oracle)
(asserts! (and (<= (get timestamp left-order) (get timestamp left-data)) (<= (get timestamp right-order) (get timestamp right-data))) err-invalid-timestamp)
(asserts! (<= (get timestamp left-data) (get timestamp right-data)) err-invalid-timestamp)
(if (get risk left-order) ;; it is risk-mgmt stop limit, i.e. buy on the way up (to hedge sell) or sell on the way down (to hedge buy)
(asserts! (if left-buy (>= (get value left-data) (get stop left-order)) (<= (get value left-data) (get stop left-order))) err-stop-not-triggered)
(asserts! (if left-buy (<= (get value left-data) (get stop left-order)) (>= (get value left-data) (get stop left-order))) err-stop-not-triggered)
)
(if (get risk right-order) ;; it is risk-mgmt stop limit, i.e. buy on the way up (to hedge sell) or sell on the way down (to hedge buy)
(asserts! (if right-buy (>= (get value right-data) (get stop right-order)) (<= (get value right-data) (get stop right-order))) err-stop-not-triggered)
(asserts! (if right-buy (<= (get value right-data) (get stop right-order)) (>= (get value right-data) (get stop right-order))) err-stop-not-triggered)
)
)
)
)
)
(asserts! (validate-authorisation left-order-fill (get maker (get user-1 users)) (get pub-key (get user-1 users)) left-order-hash left-signature) err-left-authorisation-failed)
(asserts! (validate-authorisation right-order-fill (get maker (get user-2 users)) (get pub-key (get user-2 users)) right-order-hash right-signature) err-right-authorisation-failed)
(ok
{
left-order-hash: left-order-hash,
right-order-hash: right-order-hash,
left-order-fill: left-order-fill,
right-order-fill: right-order-fill,
fillable: fillable,
left-order-make: (get maker-asset-data left-order), ;; execution is always done at left order's price
right-order-make: (get taker-asset-data left-order), ;; execution is always done at left order's price
left-sender-fee: left-sender-fee,
right-sender-fee: right-sender-fee
}
)
)
)
;; governance calls
(define-public (set-default-maker-fee (new-maker-fee uint))
(begin
(try! (is-contract-owner))
(ok (var-set default-maker-fee new-maker-fee))
)
)
(define-public (set-default-taker-fee (new-taker-fee uint))
(begin
(try! (is-contract-owner))
(ok (var-set default-taker-fee new-taker-fee))
)
)
(define-public (set-fee (user-id uint) (maker-fee uint) (taker-fee uint))
(begin
(try! (is-contract-owner))
(ok (map-set user-fee user-id { maker-fee: maker-fee, taker-fee: taker-fee }))
)
)
(define-public (set-fee-by-address (user principal) (maker-fee uint) (taker-fee uint))
(set-fee (try! (contract-call? .stxdx-registry get-user-id-or-fail user)) maker-fee taker-fee)
)
(define-public (set-paused (paused bool))
(begin
(try! (is-contract-owner))
(ok (var-set is-paused paused))
)
)
;; #[allow(unchecked_data)]
(define-public (set-trusted-oracle (pubkey (buff 33)) (trusted bool))
(begin
(try! (is-contract-owner))
(ok (map-set trusted-oracles pubkey trusted))
)
)
(define-public (set-oracle-symbol (asset-id uint) (symbol (buff 32)))
(begin
(try! (is-contract-owner))
(ok (map-set oracle-symbols asset-id symbol))
)
)
(define-public (remove-oracle-symbol (asset-id uint))
(begin
(try! (is-contract-owner))
(ok (map-delete oracle-symbols asset-id))
)
)
(define-public (set-contract-owner (new-owner principal))
(begin
(try! (is-contract-owner))
(ok (var-set contract-owner new-owner))
)
)
(define-public (set-fee-address (new-fee-address principal))
(begin
(try! (is-contract-owner))
(ok (var-set fee-address new-fee-address))
)
)
(define-public (set-authorised-sender (authorised bool) (sender principal))
(begin
(try! (is-contract-owner))
(ok (map-set authorised-senders sender authorised))
)
)
;; priviliged calls
(define-public (cancel-order
(order
{
sender: uint,
sender-fee: uint,
maker: uint,
maker-asset: uint,
taker-asset: uint,
maker-asset-data: uint,
taker-asset-data: uint,
maximum-fill: uint,
expiration-height: uint,
salt: uint,
risk: bool,
stop: uint,
timestamp: uint,
type: uint
}
)
(signature (buff 65)))
(let
(
(order-hash (hash-order order))
(cancel-hash (hash-cancel-order order-hash))
(pub-key (get pub-key (try! (contract-call? .stxdx-registry user-from-id-or-fail (get maker order)))))
)
(try! (is-authorised-sender))
(asserts!
(or
(is-eq type-order-fok (get type order))
(is-eq type-order-ioc (get type order))
(is-eq (secp256k1-recover? (sha256 (concat structured-data-prefix (concat (message-domain) cancel-hash))) signature) (ok pub-key))
)
err-cancel-authorisation-failed
)
;; cancel means no more fill, so setting its fill to maximum-fill achieve it.
(contract-call? .stxdx-registry set-order-fill order-hash (get maximum-fill order))
)
)
(define-public (cancel-order-many
(cancel-order-list
(list 200
{
order: { sender: uint, sender-fee: uint, maker: uint, maker-asset: uint, taker-asset: uint, maker-asset-data: uint, taker-asset-data: uint, maximum-fill: uint, expiration-height: uint, salt: uint, risk: bool, stop: uint, timestamp: uint, type: uint },
signature: (buff 65)
}
)
))
(ok (map cancel-order-iter cancel-order-list))
)
;; public calls
(define-public (approve-order
(order
{
sender: uint,
sender-fee: uint,
maker: uint,
maker-asset: uint,
taker-asset: uint,
maker-asset-data: uint,
taker-asset-data: uint,
maximum-fill: uint,
expiration-height: uint,
salt: uint,
risk: bool,
stop: uint,
timestamp: uint,
type: uint
}
)
)
(begin
(asserts! (not (var-get is-paused)) err-paused)
(asserts! (is-eq (try! (contract-call? .stxdx-registry user-maker-from-id-or-fail (get maker order))) tx-sender) err-maker-not-tx-sender)
(contract-call? .stxdx-registry set-order-approval (hash-order order) true)
)
)
(define-public (match-orders
(left-order
{
sender: uint,
sender-fee: uint,
maker: uint,
maker-asset: uint,
taker-asset: uint,
maker-asset-data: uint,
taker-asset-data: uint,
maximum-fill: uint,
expiration-height: uint,
salt: uint,
risk: bool,
stop: uint,
timestamp: uint,
type: uint
}
)
(right-order
{
sender: uint,
sender-fee: uint,
maker: uint,
maker-asset: uint,
taker-asset: uint,
maker-asset-data: uint,
taker-asset-data: uint,
maximum-fill: uint,
expiration-height: uint,
salt: uint,
risk: bool,
stop: uint,
timestamp: uint,
type: uint
}
)
(left-signature (buff 65))
(right-signature (buff 65))
(left-oracle-data (optional { timestamp: uint, value: uint, signature: (buff 65) }))
(right-oracle-data (optional { timestamp: uint, value: uint, signature: (buff 65) }))
(fill (optional uint))
)
(let
(
(validation-data (try! (validate-match left-order right-order left-signature right-signature left-oracle-data right-oracle-data fill)))
(fillable (match fill value value (get fillable validation-data)))
(left-order-make (get left-order-make validation-data))
(right-order-make (get right-order-make validation-data))
(left-sender-fee (get left-sender-fee validation-data))
(right-sender-fee (get right-sender-fee validation-data))
)
(asserts! (not (var-get is-paused)) err-paused)
(and
(not (is-order-triggered (get left-order-hash validation-data)))
(map-set triggered-orders
(get left-order-hash validation-data)
{
triggered: true,
timestamp: (match left-oracle-data value (get timestamp value) (get timestamp left-order))
}
)
)
(and
(not (is-order-triggered (get right-order-hash validation-data)))
(map-set triggered-orders
(get right-order-hash validation-data)
{
triggered: true,
timestamp: (match right-oracle-data value (get timestamp value) (get timestamp right-order))
}
)
)
(try! (settle-order left-order (* fillable left-order-make) (get maker right-order) left-sender-fee))
(try! (settle-order right-order (* fillable right-order-make) (get maker left-order) right-sender-fee))
(try! (contract-call? .stxdx-registry set-two-order-fills (get left-order-hash validation-data) (+ (get left-order-fill validation-data) fillable) (get right-order-hash validation-data) (+ (get right-order-fill validation-data) fillable)))
(ok
{
fillable: fillable,
left-order-make: left-order-make,
right-order-make: right-order-make,
left-sender-fee: left-sender-fee,
right-sender-fee: right-sender-fee
}
)
)
)
;; private calls
(define-private (cancel-order-iter
(one-cancel-order
{
order: { sender: uint, sender-fee: uint, maker: uint, maker-asset: uint, taker-asset: uint, maker-asset-data: uint, taker-asset-data: uint, maximum-fill: uint, expiration-height: uint, salt: uint, risk: bool, stop: uint, timestamp: uint, type: uint },
signature: (buff 65)
}
))
(cancel-order (get order one-cancel-order) (get signature one-cancel-order))
)
(define-private (is-contract-owner)
(ok (asserts! (is-eq (var-get contract-owner) tx-sender) err-unauthorised-caller))
)
(define-private (is-authorised-sender)
(ok (asserts! (default-to false (map-get? authorised-senders contract-caller)) err-unauthorised-sender))
)
(define-private (validate-authorisation (fills uint) (maker principal) (pub-key (buff 33)) (hash (buff 32)) (signature (buff 65)))
(begin
(or
(> fills u0)
(is-eq maker tx-sender)
(and (is-eq (len signature) u0) (contract-call? .stxdx-registry get-order-approval maker hash))
(is-eq (secp256k1-recover? (sha256 (concat structured-data-prefix (concat (message-domain) hash))) signature) (ok pub-key))
)
)
)
(define-private (settle-order
(order
{
sender: uint,
sender-fee: uint,
maker: uint,
maker-asset: uint,
taker-asset: uint,
maker-asset-data: uint,
taker-asset-data: uint,
maximum-fill: uint,
expiration-height: uint,
salt: uint,
risk: bool,
stop: uint,
timestamp: uint,
type: uint
}
)
(amount uint)
(taker uint)
(fee uint)
)
(begin
(as-contract (unwrap! (contract-call? .stxdx-wallet-zero transfer amount (get maker order) taker (get maker-asset order)) err-asset-contract-call-failed))
(let
(
(fee-address-id (try! (contract-call? .stxdx-registry get-user-id-or-fail (var-get fee-address))))
)
(and
(> fee u0)
(as-contract (unwrap! (contract-call? .stxdx-wallet-zero transfer (mul-down fee amount) (get maker order) fee-address-id (get maker-asset order)) err-sender-fee-payment-failed))
)
)
(ok true)
)
)
(define-private (min (a uint) (b uint))
(if (< a b) a b)
)
(define-read-only (mul-down (a uint) (b uint))
(/ (* a b) ONE_8)
)
(define-private (max (a uint) (b uint))
(if (<= a b) b a)
)

216
stxdx-registry.clar Normal file
View File

@@ -0,0 +1,216 @@
(impl-trait .trait-ownable.ownable-trait)
;; 4000-4999: registry errors
(define-constant err-unauthorised-caller (err u4000))
(define-constant err-unauthorised-sender (err u4001))
(define-constant err-storage-failure (err u4002))
(define-constant err-unknown-user-id (err u3500))
(define-constant err-unknown-asset-id (err u3501))
(define-constant err-user-already-registered (err u3502))
(define-constant err-asset-already-registered (err u3503))
(define-constant err-not-whitelisted (err u3504))
(define-map order-fills (buff 32) uint)
(define-map order-approvals {maker: principal, order-hash: (buff 32)} bool)
(define-map authorised-exchanges principal bool)
(define-map whitelisted-users principal bool)
(define-data-var contract-owner principal tx-sender)
(define-data-var use-whitelist bool false)
(define-read-only (is-whitelisted (user principal))
(default-to false (map-get? whitelisted-users user))
)
(define-public (apply-whitelist (new-use-whitelist bool))
(begin
(try! (is-contract-owner))
(ok (var-set use-whitelist new-use-whitelist))
)
)
(define-public (whitelist (user principal) (whitelisted bool))
(begin
(try! (is-contract-owner))
(ok (map-set whitelisted-users user whitelisted))
)
)
(define-public (whitelist-many (users (list 2000 principal)) (whitelisted (list 2000 bool)))
(ok (map whitelist users whitelisted))
)
(define-read-only (get-contract-owner)
(ok (var-get contract-owner))
)
(define-public (set-contract-owner (new-owner principal))
(begin
(asserts! (is-eq (var-get contract-owner) tx-sender) err-unauthorised-sender)
(ok (var-set contract-owner new-owner))
)
)
(define-private (is-contract-owner)
(ok (asserts! (is-eq (var-get contract-owner) tx-sender) err-unauthorised-caller))
)
(define-map user-registry
uint
{
maker: principal,
pub-key: (buff 33)
}
)
(define-data-var user-registry-nonce uint u0)
(define-map user-id-registry principal uint)
(define-map asset-registry uint principal)
(define-map asset-registry-ids principal uint)
(define-data-var asset-registry-nonce uint u0)
(define-public (register-asset (asset principal))
(let
(
(asset-id (+ (var-get asset-registry-nonce) u1))
)
(try! (is-contract-owner))
(asserts! (map-insert asset-registry-ids asset asset-id) err-asset-already-registered)
(map-insert asset-registry asset-id asset)
(var-set asset-registry-nonce asset-id)
(ok asset-id)
)
)
(define-public (register-user-on-behalf (pub-key (buff 33)) (maker principal))
(begin
(try! (is-contract-owner))
(register-user-given-maker pub-key maker)
)
)
(define-public (register-user (pub-key (buff 33)))
(begin
(asserts! (or (not (var-get use-whitelist)) (is-whitelisted tx-sender)) err-not-whitelisted)
(register-user-given-maker pub-key tx-sender)
)
)
(define-private (register-user-given-maker (pub-key (buff 33)) (maker principal))
(let
(
(reg-id (+ (var-get user-registry-nonce) u1))
)
(asserts! (map-insert user-id-registry maker reg-id) err-user-already-registered)
(map-insert user-registry reg-id {maker: maker, pub-key: pub-key})
(var-set user-registry-nonce reg-id)
(ok reg-id)
)
)
(define-read-only (get-user-id (user principal))
(map-get? user-id-registry user)
)
(define-read-only (get-user-id-or-fail (user principal))
(ok (unwrap! (map-get? user-id-registry user) err-unknown-user-id))
)
(define-read-only (user-from-id (id uint))
(map-get? user-registry id)
)
(define-read-only (user-from-id-or-fail (id uint))
(ok (unwrap! (map-get? user-registry id) err-unknown-user-id))
)
(define-read-only (get-two-users-from-id-or-fail (id-1 uint) (id-2 uint))
(ok {
user-1: (unwrap! (map-get? user-registry id-1) err-unknown-user-id),
user-2: (unwrap! (map-get? user-registry id-2) err-unknown-user-id)
})
)
(define-read-only (user-maker-from-id (id uint))
(get maker (map-get? user-registry id))
)
(define-read-only (user-maker-from-id-or-fail (id uint))
(ok (get maker (unwrap! (map-get? user-registry id) err-unknown-user-id)))
)
(define-read-only (asset-from-id (id uint))
(map-get? asset-registry id)
)
(define-read-only (get-asset-id (asset principal))
(map-get? asset-registry-ids asset)
)
(define-read-only (asset-from-id-or-fail (id uint))
(ok (unwrap! (map-get? asset-registry id) err-unknown-asset-id))
)
(define-private (valid-exchange-caller)
(ok (asserts! (is-approved-exchange contract-caller) err-unauthorised-caller))
)
(define-read-only (get-order-fill (order-hash (buff 32)))
(default-to u0 (map-get? order-fills order-hash))
)
(define-read-only (get-two-order-fills (order-hash-1 (buff 32)) (order-hash-2 (buff 32)))
{
order-1: (default-to u0 (map-get? order-fills order-hash-1)),
order-2: (default-to u0 (map-get? order-fills order-hash-2))
}
)
(define-read-only (get-order-fills (order-hashes (list 200 (buff 32))))
(map get-order-fill order-hashes)
)
(define-public (set-order-fill (order-hash (buff 32)) (new-fill uint))
(begin
(try! (valid-exchange-caller))
(ok (asserts! (map-set order-fills order-hash new-fill) err-storage-failure))
)
)
(define-public (set-two-order-fills (order-hash-1 (buff 32)) (new-fill-1 uint) (order-hash-2 (buff 32)) (new-fill-2 uint))
(begin
(try! (valid-exchange-caller))
(ok (asserts! (and (map-set order-fills order-hash-1 new-fill-1) (map-set order-fills order-hash-2 new-fill-2)) err-storage-failure))
)
)
(define-private (set-order-fills-iter (item {order-hash: (buff 32), new-fill: uint}) (prev bool))
(and prev (map-set order-fills (get order-hash item) (get new-fill item)))
)
(define-public (set-order-fills (fills (list 200 {order-hash: (buff 32), new-fill: uint})))
(begin
(try! (valid-exchange-caller))
(ok (asserts! (fold set-order-fills-iter fills true) err-storage-failure))
)
)
(define-read-only (get-order-approval (maker principal) (order-hash (buff 32)))
(default-to false (map-get? order-approvals {maker: maker, order-hash: order-hash}))
)
(define-public (set-order-approval (order-hash (buff 32)) (approved bool))
(ok (map-set order-approvals {maker: tx-sender, order-hash: order-hash} approved))
)
(define-public (approve-exchange (exchange principal) (approved bool))
(begin
(try! (is-contract-owner))
(ok (map-set authorised-exchanges exchange approved))
)
)
(define-read-only (is-approved-exchange (exchange principal))
(default-to false (map-get? authorised-exchanges exchange))
)

84
stxdx-sender-proxy.clar Normal file
View File

@@ -0,0 +1,84 @@
(impl-trait .trait-ownable.ownable-trait)
(define-data-var contract-owner principal tx-sender)
(define-map authorised-senders principal bool)
;; 7000-7999: proxy errors
(define-constant err-unauthorised-caller (err u7000))
(define-constant err-unauthorised-sender (err u7001))
(define-private (is-contract-owner)
(ok (asserts! (is-eq (var-get contract-owner) tx-sender) err-unauthorised-caller))
)
(define-public (set-contract-owner (new-owner principal))
(begin
(try! (is-contract-owner))
(ok (var-set contract-owner new-owner))
)
)
(define-read-only (get-contract-owner)
(ok (var-get contract-owner))
)
(define-public (set-authorised-sender (authorised bool) (sender principal))
(begin
(try! (is-contract-owner))
(ok (map-set authorised-senders sender authorised))
)
)
(define-private (is-authorised-sender)
(ok (asserts! (default-to false (map-get? authorised-senders contract-caller)) err-unauthorised-sender))
)
(define-public (match-orders
(left-order { sender: uint, sender-fee: uint, maker: uint, maker-asset: uint, taker-asset: uint, maker-asset-data: uint, taker-asset-data: uint, maximum-fill: uint, expiration-height: uint, salt: uint, risk: bool, stop: uint, timestamp: uint, type: uint })
(right-order { sender: uint, sender-fee: uint, maker: uint, maker-asset: uint, taker-asset: uint, maker-asset-data: uint, taker-asset-data: uint, maximum-fill: uint, expiration-height: uint, salt: uint, risk: bool, stop: uint, timestamp: uint, type: uint })
(left-signature (buff 65))
(right-signature (buff 65))
(left-oracle-data (optional { timestamp: uint, value: uint, signature: (buff 65) }))
(right-oracle-data (optional { timestamp: uint, value: uint, signature: (buff 65) }))
(fill (optional uint)))
(begin
(try! (is-authorised-sender))
(as-contract (contract-call? .stxdx-exchange-zero match-orders left-order right-order left-signature right-signature left-oracle-data right-oracle-data fill))
)
)
(define-private (match-orders-iter
(matched-orders
{
left-order: { sender: uint, sender-fee: uint, maker: uint, maker-asset: uint, taker-asset: uint, maker-asset-data: uint, taker-asset-data: uint, maximum-fill: uint, expiration-height: uint, salt: uint, risk: bool, stop: uint, timestamp: uint, type: uint },
right-order: { sender: uint, sender-fee: uint, maker: uint, maker-asset: uint, taker-asset: uint, maker-asset-data: uint, taker-asset-data: uint, maximum-fill: uint, expiration-height: uint, salt: uint, risk: bool, stop: uint, timestamp: uint, type: uint },
left-signature: (buff 65),
right-signature: (buff 65),
left-oracle-data: (optional { timestamp: uint, value: uint, signature: (buff 65) }),
right-oracle-data: (optional { timestamp: uint, value: uint, signature: (buff 65) }),
fill: (optional uint)
}
))
(as-contract (contract-call? .stxdx-exchange-zero match-orders (get left-order matched-orders) (get right-order matched-orders) (get left-signature matched-orders) (get right-signature matched-orders) (get left-oracle-data matched-orders) (get right-oracle-data matched-orders) (get fill matched-orders)))
)
(define-public (match-orders-many
(matched-orders-list
(list 200
{
left-order: { sender: uint, sender-fee: uint, maker: uint, maker-asset: uint, taker-asset: uint, maker-asset-data: uint, taker-asset-data: uint, maximum-fill: uint, expiration-height: uint, salt: uint, risk: bool, stop: uint, timestamp: uint, type: uint },
right-order: { sender: uint, sender-fee: uint, maker: uint, maker-asset: uint, taker-asset: uint, maker-asset-data: uint, taker-asset-data: uint, maximum-fill: uint, expiration-height: uint, salt: uint, risk: bool, stop: uint, timestamp: uint, type: uint },
left-signature: (buff 65),
right-signature: (buff 65),
left-oracle-data: (optional { timestamp: uint, value: uint, signature: (buff 65) }),
right-oracle-data: (optional { timestamp: uint, value: uint, signature: (buff 65) }),
fill: (optional uint)
}
)
))
(begin
(try! (is-authorised-sender))
(ok (map match-orders-iter matched-orders-list))
)
)

47
stxdx-utils.clar Normal file
View File

@@ -0,0 +1,47 @@
(define-constant byte-list 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff)
(define-read-only (byte-to-uint (byte (buff 1)))
(unwrap-panic (index-of byte-list byte))
)
(define-read-only (buff-to-uint (bytes (buff 16)))
(+
(match (element-at bytes u0) byte (byte-to-uint byte) u0)
(match (element-at bytes u1) byte (* (byte-to-uint byte) u256) u0)
(match (element-at bytes u2) byte (* (byte-to-uint byte) u65536) u0)
(match (element-at bytes u3) byte (* (byte-to-uint byte) u16777216) u0)
(match (element-at bytes u4) byte (* (byte-to-uint byte) u4294967296) u0)
(match (element-at bytes u5) byte (* (byte-to-uint byte) u1099511627776) u0)
(match (element-at bytes u6) byte (* (byte-to-uint byte) u281474976710656) u0)
(match (element-at bytes u7) byte (* (byte-to-uint byte) u72057594037927936) u0)
(match (element-at bytes u8) byte (* (byte-to-uint byte) u18446744073709551616) u0)
(match (element-at bytes u9) byte (* (byte-to-uint byte) u4722366482869645213696) u0)
(match (element-at bytes u10) byte (* (byte-to-uint byte) u1208925819614629174706176) u0)
(match (element-at bytes u11) byte (* (byte-to-uint byte) u309485009821345068724781056) u0)
(match (element-at bytes u12) byte (* (byte-to-uint byte) u79228162514264337593543950336) u0)
(match (element-at bytes u13) byte (* (byte-to-uint byte) u20282409603651670423947251286016) u0)
(match (element-at bytes u14) byte (* (byte-to-uint byte) u5192296858534827628530496329220096) u0)
(match (element-at bytes u15) byte (* (byte-to-uint byte) u1329227995784915872903807060280344576) u0)
)
)
(define-private (buff-slice-iterator (byte (buff 1)) (state {accumulator: (buff 256), index: uint, start: uint, end: uint}))
(let
(
(start (get start state))
(end (get end state))
(index (get index state))
(accumulator (get accumulator state))
)
{
start: start,
end: end,
accumulator: (if (and (>= index start) (< index end)) (unwrap-panic (as-max-len? (concat accumulator byte) u256)) accumulator),
index: (+ index u1)
}
)
)
(define-read-only (buff-slice (bytes (buff 256)) (start uint) (end uint))
(get accumulator (fold buff-slice-iterator bytes {accumulator: 0x, index: u0, start: start, end: end}))
)

234
stxdx-wallet-zero.clar Normal file
View File

@@ -0,0 +1,234 @@
(impl-trait .trait-ownable.ownable-trait)
(use-trait sip010-trait .trait-sip-010.sip-010-trait)
;; 6000-6999: wallet errors
(define-constant err-unauthorised-caller (err u6000))
(define-constant err-unauthorised-sender (err u6001))
(define-constant err-unknown-request-id (err u6002))
(define-constant err-unauthorised-request (err u6003))
(define-constant err-amount-exceeds-balance (err u6004))
(define-constant err-invalid-grace-period (err u6005))
(define-constant err-unknown-asset-id (err u3501))
(define-data-var contract-owner principal tx-sender)
(define-map authorised-approvers principal bool)
(define-map authorised-exchanges principal bool)
(define-map user-balance
{
user-id: uint,
asset-id: uint
}
uint
)
(define-constant max-grace-period u1008)
(define-data-var request-grace-period uint u100)
(define-data-var request-nonce uint u0)
(define-map requests
uint
{
amount: uint,
user-id: uint,
asset-id: uint,
asset: principal,
request-block: uint,
approved: bool,
transferred-block: uint
}
)
(define-public (set-request-grace-period (new-grace-period uint))
(begin
(try! (is-contract-owner))
(asserts! (>= max-grace-period new-grace-period) err-invalid-grace-period)
(ok (var-set request-grace-period new-grace-period))
)
)
(define-read-only (get-request-grace-period)
(ok (var-get request-grace-period))
)
(define-read-only (get-request-or-fail (request-id uint))
(ok (unwrap! (map-get? requests request-id) err-unknown-request-id))
)
(define-private (is-contract-owner)
(ok (asserts! (is-eq (var-get contract-owner) tx-sender) err-unauthorised-caller))
)
(define-public (set-contract-owner (new-owner principal))
(begin
(try! (is-contract-owner))
(ok (var-set contract-owner new-owner))
)
)
(define-read-only (get-contract-owner)
(ok (var-get contract-owner))
)
(define-public (set-authorised-approver (authorised bool) (sender principal))
(begin
(try! (is-contract-owner))
(ok (map-set authorised-approvers sender authorised))
)
)
(define-private (is-authorised-approver)
(ok (asserts! (default-to false (map-get? authorised-approvers tx-sender)) err-unauthorised-caller))
)
(define-public (approve-exchange (exchange principal) (approved bool))
(begin
(try! (is-contract-owner))
(ok (map-set authorised-exchanges exchange approved))
)
)
(define-read-only (is-approved-exchange (exchange principal))
(default-to false (map-get? authorised-exchanges exchange))
)
(define-read-only (get-user-balance-or-default (user-id uint) (asset-id uint))
(default-to u0 (map-get? user-balance { user-id: user-id, asset-id: asset-id }))
)
(define-public (transfer-in-many (user-id uint) (amounts (list 10 uint)) (asset-ids (list 10 uint)) (asset-traits (list 10 <sip010-trait>)))
(ok
(map transfer-in
amounts
(list user-id user-id user-id user-id user-id user-id user-id user-id user-id user-id)
asset-ids
asset-traits
)
)
)
(define-public (transfer-in (amount uint) (user-id uint) (asset-id uint) (asset-trait <sip010-trait>))
(begin
(asserts! (is-eq (try! (contract-call? .stxdx-registry asset-from-id-or-fail asset-id)) (contract-of asset-trait)) err-unknown-asset-id)
(try! (contract-call? asset-trait transfer-fixed amount tx-sender (as-contract tx-sender) none))
(map-set user-balance { user-id: user-id, asset-id: asset-id } (+ (get-user-balance-or-default user-id asset-id) amount))
(print {type: "transfer_in", asset-id: asset-id, amount: amount, user-id: user-id, sender: tx-sender})
(ok true)
)
)
(define-public (request-transfer-out-many (user-id uint) (amounts (list 10 uint)) (asset-ids (list 10 uint)) (assets (list 10 principal)))
(ok
(map request-transfer-out
amounts
(list user-id user-id user-id user-id user-id user-id user-id user-id user-id user-id)
asset-ids
assets
)
)
)
(define-public (request-transfer-out (amount uint) (user-id uint) (asset-id uint) (asset principal))
(let
(
(user (try! (contract-call? .stxdx-registry user-from-id-or-fail user-id)))
(request-id (+ (var-get request-nonce) u1))
)
(asserts! (is-eq (try! (contract-call? .stxdx-registry asset-from-id-or-fail asset-id)) asset) err-unknown-asset-id)
(asserts! (is-eq tx-sender (get maker user)) err-unauthorised-caller)
(asserts! (<= amount (get-user-balance-or-default user-id asset-id)) err-amount-exceeds-balance)
(map-set requests request-id {
amount: amount,
user-id: user-id,
asset-id: asset-id,
asset: asset,
request-block: block-height,
approved: false,
transferred-block: u340282366920938463463374607431768211455
})
(var-set request-nonce request-id)
(print {type: "request_transfer_out", request-id: request-id, user-id: user-id, asset-id: asset-id, amount: amount})
(ok request-id)
)
)
(define-public (approve-transfer-out (request-id uint) (approved bool))
(begin
(asserts! (or (is-ok (is-authorised-approver)) (is-ok (is-contract-owner))) err-unauthorised-caller)
(print {type: "approve_transfer_out", request-id: request-id, approved: approved})
(ok (map-set requests request-id (merge (try! (get-request-or-fail request-id)) { approved: approved })))
)
)
(define-public (approve-and-transfer-out (request-id uint) (asset-trait <sip010-trait>))
(begin
(try! (approve-transfer-out request-id true))
(transfer-out request-id asset-trait)
)
)
(define-public (approve-and-transfer-out-many (asset-trait <sip010-trait>) (request-ids (list 200 uint)))
(ok
(map approve-and-transfer-out
request-ids
(list
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait asset-trait
)
)
)
)
(define-public (transfer-out (request-id uint) (asset-trait <sip010-trait>))
(let
(
(request (try! (get-request-or-fail request-id)))
(user (try! (contract-call? .stxdx-registry user-from-id-or-fail (get user-id request))))
)
(asserts! (is-eq (get asset request) (contract-of asset-trait)) err-unknown-asset-id)
(asserts! (or (is-ok (is-authorised-approver)) (is-eq tx-sender (get maker user)) (is-ok (is-contract-owner))) err-unauthorised-caller)
(asserts! (or (get approved request) (>= block-height (+ (get request-block request) (var-get request-grace-period)))) err-unauthorised-request)
(asserts! (> (get transferred-block request) block-height) err-unauthorised-request)
(asserts! (<= (get amount request) (get-user-balance-or-default (get user-id request) (get asset-id request))) err-amount-exceeds-balance)
(map-set user-balance { user-id: (get user-id request), asset-id: (get asset-id request) } (- (get-user-balance-or-default (get user-id request) (get asset-id request)) (get amount request)))
(map-set requests request-id (merge request { transferred-block: block-height }))
(as-contract (try! (contract-call? asset-trait transfer-fixed (get amount request) tx-sender (get maker user) none)))
(print {type: "transfer_out", request-id: request-id, user-id: (get user-id request), asset-id: (get asset-id request), amount: (get amount request)})
(ok true)
)
)
(define-public (transfer (amount uint) (sender-id uint) (recipient-id uint) (asset-id uint))
(let
(
(sender (try! (contract-call? .stxdx-registry user-from-id-or-fail sender-id)))
)
(asserts! (or (is-approved-exchange contract-caller) (is-eq tx-sender (get maker sender))) err-unauthorised-caller)
(asserts! (<= amount (get-user-balance-or-default sender-id asset-id)) err-amount-exceeds-balance)
(map-set user-balance { user-id: sender-id, asset-id: asset-id } (- (get-user-balance-or-default sender-id asset-id) amount))
(map-set user-balance { user-id: recipient-id, asset-id: asset-id } (+ (get-user-balance-or-default recipient-id asset-id) amount))
(print {type: "internal_transfer", asset-id: asset-id, amount: amount, sender-id: sender-id, recipient-id: recipient-id})
(ok true)
)
)