Source Code

;; SPDX-License-Identifier: BUSL-1.1

;; =================================
;; Self Listing Helper V3A
;; =================================
;;
;; This contract enables permissionless creation of AMM pools through two main paths:
;;
;; 1. Standard Pool Creation (create):
;;    - For tokens that are already approved in the system
;;    - Requires token-x to be whitelisted and have sufficient balance
;;    - Enforces standard pool parameters and security checks
;;
;; 2. Permissionless Pool Creation (create2):
;;    - Allows creation of pools with new, unregistered tokens
;;    - Uses a verification system to ensure token contract legitimacy:
;;      a. Verifies the token contract deployment on Stacks blockchain
;;      b. Checks contract code matches an approved template
;;      c. Validates deployment proof using Stacks block headers
;;    - The verify-deploy mechanism works by:
;;      1. Taking deployment transaction proof from Stacks blockchain
;;      2. Verifying the contract code matches a whitelisted template
;;      3. Confirming the deployment transaction was properly mined
;;      This ensures only legitimate token contracts can be used
;;
;; Additional Features:
;; - Liquidity locking/burning mechanisms
;; - Fee rebate management
;; - Token approval governance
;; - Pool parameter configuration
;;
;; The contract combines permissioned and permissionless approaches to enable
;; safe, flexible AMM pool creation while maintaining system security.

(use-trait ft-trait 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.trait-sip-010.sip-010-trait)

;; Constants from code-body-prover
(define-constant chain-id-bytes (unwrap-panic (slice? (unwrap-panic (to-consensus-buff? chain-id)) u13 u17)))
(define-constant tx-version (if is-in-mainnet 0x00 0x80))
(define-constant auth-type-standard 0x04)
(define-constant hash-mode-p2pkh 0x00)
(define-constant key-encoding 0x00)
(define-constant anchor-mode 0x03)
(define-constant post-conditions-mode-deny 0x02)
(define-constant versioned-smart-contract 0x06)
(define-constant clarity-version 0x03)

(define-constant err-invalid-length-nonce (err u2000))
(define-constant err-invalid-length-fee (err u2001))
(define-constant err-invalid-length-signature (err u2002))
(define-constant err-invalid-principal-version (err u2003))
(define-constant err-principal-not-contract (err u2004))

;; Constants from self-listing-helper-v3
(define-constant err-not-authorised (err u1000))
(define-constant err-token-not-approved (err u1002))
(define-constant err-insufficient-balance (err u1003))
(define-constant err-pool-exists (err u1004))
(define-constant err-invalid-lock-parameter (err u1005))

(define-constant ONE_8 u100000000)
(define-constant MAX_UINT u340282366920938463463374607431768211455)

(define-constant NONE 0x)
(define-constant LOCK 0x01)
(define-constant BURN 0x02)

;; Data variables and maps
(define-data-var wrapped-token-template (list 20 (string-ascii 5000)) (list))
(define-map approved-token-x principal { approved: bool, min-x: uint })
(define-data-var fee-rebate uint u50000000)
(define-map wrap-token-map principal principal)

;; Functions from code-body-prover
(define-read-only (contract-name-length-byte (length uint))
	(unwrap-panic (slice? (unwrap-panic (to-consensus-buff? length)) u16 u17))
)

(define-read-only (contract-code-length-length-bytes (length uint))
	(unwrap-panic (slice? (unwrap-panic (to-consensus-buff? length)) u13 u17))
)

(define-read-only (string-to-buff (str (string-ascii 80)))
	(unwrap-panic (slice? (unwrap-panic (to-consensus-buff? str)) u5 (+ (len str) u5)))
)

(define-read-only (calculate-txid
	(nonce (buff 8))
	(fee (buff 8))
	(signature (buff 65))
	(contract principal)
	(code-body (buff 80000))
	)
	(let
		(
			(principal-data (unwrap! (principal-destruct? contract) err-invalid-principal-version))
			(contract-name (unwrap! (get name principal-data) err-principal-not-contract))
		)
		(asserts! (is-eq (len nonce) u8) err-invalid-length-nonce)
		(asserts! (is-eq (len fee) u8) err-invalid-length-fee)
		(asserts! (is-eq (len signature) u65) err-invalid-length-signature)
		(ok (sha512/256
			(concat tx-version
			(concat chain-id-bytes
			(concat auth-type-standard
			(concat hash-mode-p2pkh
			(concat (get hash-bytes principal-data)
			(concat nonce
			(concat fee
			(concat key-encoding
			(concat signature
			(concat anchor-mode
			(concat post-conditions-mode-deny
			(concat 0x00000000 ;; no post conditions
			(concat versioned-smart-contract
			(concat clarity-version
			(concat (contract-name-length-byte (len contract-name))
			(concat (string-to-buff contract-name)
			(concat (contract-code-length-length-bytes (len code-body)) code-body
			)))))))))))))))))
		))
	)
)

;; Functions from self-listing-helper-v3
(define-read-only (is-dao-or-extension)
  (ok (asserts! (or (is-eq tx-sender 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.executor-dao) (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.executor-dao is-extension contract-caller)) err-not-authorised)))

(define-read-only (get-approved-token-x-or-default (token-x principal))
    (default-to { approved: false, min-x: MAX_UINT } (map-get? approved-token-x token-x)))

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

(define-read-only (get-lock-period)
	(contract-call? .liquidity-locker get-lock-period))

(define-read-only (get-locked-liquidity-or-default (owner principal) (pool-id uint))
	(contract-call? .liquidity-locker get-locked-liquidity-or-default owner pool-id))

(define-read-only (get-locked-liquidity-for-pool-or-default (pool-id uint))
  (contract-call? .liquidity-locker get-locked-liquidity-for-pool-or-default pool-id))

(define-read-only (get-burnt-liquidity-or-default (pool-id uint))
	(contract-call? .liquidity-locker get-burnt-liquidity-or-default pool-id))

(define-read-only (get-wrapped-token-contract-code (token principal))
  (let ((token-str (unwrap-panic (as-max-len? (principal-to-string token) u100)))
        (template (var-get wrapped-token-template)))
    (get result (fold join-template-parts-iter 
      template 
      {token: token-str, result: ""}))))

(define-read-only (principal-to-string (p principal))
	(let (
			(destructed (match (principal-destruct? p) ok-value ok-value err-value err-value))
			(checksum (unwrap-panic (slice? (sha256 (sha256 (concat (get version destructed) (get hash-bytes destructed)))) u0 u4)))
			(data (unwrap-panic (as-max-len? (concat (get hash-bytes destructed) checksum) u24)))
			(result (concat (concat "S" (unwrap-panic (element-at? C32 (buff-to-uint-be (get version destructed))))) (append-leading-0 data (trim-leading-0 (hash-bytes-to-string data))))))
		(match (get name destructed) n (concat (concat result ".") n) result)))

(define-read-only (verify-deploy
	(verify-params {
		nonce: (buff 8),
		fee-rate: (buff 8),
		signature: (buff 65),
		contract: principal,
		token-y: principal,
		proof: { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint},
		tx-block-height: uint,
		block-header-without-signer-signatures: (buff 712) }))
	(contract-call? .clarity-stacks was-tx-mined-compact
		(try! (calculate-txid (get nonce verify-params) (get fee-rate verify-params) (get signature verify-params) (get contract verify-params) (contract-call? .clarity-stacks-helper string-ascii-to-buffer (get-wrapped-token-contract-code (get token-y verify-params)))))
		(get proof verify-params)
		(get tx-block-height verify-params)
		(get block-header-without-signer-signatures verify-params)))

;; Public functions
(define-public (create
  (request-details {
		token-x-trait: <ft-trait>, token-y-trait: <ft-trait>,
		factor: uint,
		bal-x: uint, bal-y: uint,
		fee-rate-x: uint, fee-rate-y: uint,
		max-in-ratio: uint, max-out-ratio: uint,
		threshold-x: uint, threshold-y: uint,
		oracle-enabled: bool, oracle-average: uint,
		start-block: uint,
		lock: (buff 1) }))
  (let (
			(token-y-trait (get token-y-trait request-details)))
		(try! (pre-check request-details))
		(asserts! (< u0 (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01 get-reserve (contract-of token-y-trait))) err-token-not-approved)
		(print { type: "create", request: request-details })
		(post-check request-details)))
		
(define-public (create2
    (request-details {
        token-x-trait: <ft-trait>, token-y-trait: <ft-trait>,
				factor: uint,
        bal-x: uint, bal-y: uint,
        fee-rate-x: uint, fee-rate-y: uint,
        max-in-ratio: uint, max-out-ratio: uint,
        threshold-x: uint, threshold-y: uint,
        oracle-enabled: bool, oracle-average: uint,
        start-block: uint,
				lock: (buff 1) })
		(verify-params {
			nonce: (buff 8),
			fee-rate: (buff 8),
			signature: (buff 65),
			contract: principal,
			token-y: principal,
			proof: { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint},
			tx-block-height: uint,
			block-header-without-signer-signatures: (buff 712) }))
  (let ((token-y-trait (get token-y-trait request-details)))
		(asserts! (is-eq (contract-of token-y-trait) (get contract verify-params)) err-token-not-approved)
		(try! (pre-check request-details))
		(try! (verify-deploy verify-params))
		(try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01 set-approved-token (contract-of token-y-trait) true))
		(map-set wrap-token-map (get token-y verify-params) (get contract verify-params))
		(print { type: "create2", request: request-details, verify: verify-params })
		(post-check request-details)))

(define-public (lock-liquidity (amount uint) (pool-id uint))
	(contract-call? .liquidity-locker lock-liquidity amount pool-id))

(define-public (burn-liquidity (amount uint) (pool-id uint))
	(contract-call? .liquidity-locker burn-liquidity amount pool-id))

(define-public (claim-liquidity (pool-id uint))
	(contract-call? .liquidity-locker claim-liquidity pool-id))

;; Governance calls
(define-public (approve-token-x (token principal) (approved bool) (min-x uint))
    (begin
        (try! (is-dao-or-extension))
        (ok (map-set approved-token-x token { approved: approved, min-x: min-x }))))

(define-public (set-fee-rebate (new-fee-rebate uint))
	(begin 
		(try! (is-dao-or-extension))
		(ok (var-set fee-rebate new-fee-rebate))))

(define-public (set-wrapped-token-template (new-template (list 20 (string-ascii 5000))))
  (begin
    (try! (is-dao-or-extension))
    (ok (var-set wrapped-token-template new-template))))

;; Private functions
(define-private (pre-check 
	(request-details {
		token-x-trait: <ft-trait>, token-y-trait: <ft-trait>,
		factor: uint,
		bal-x: uint, bal-y: uint,
		fee-rate-x: uint, fee-rate-y: uint,
		max-in-ratio: uint, max-out-ratio: uint,
		threshold-x: uint, threshold-y: uint,
		oracle-enabled: bool, oracle-average: uint,
		start-block: uint,
		lock: (buff 1) }))
	(let (
			(token-x-trait (get token-x-trait request-details))
			(token-y-trait (get token-y-trait request-details))
			(token-x-details (get-approved-token-x-or-default (contract-of token-x-trait))))
		(asserts! (get approved token-x-details) err-token-not-approved)
    (asserts! (>= (get bal-x request-details) (get min-x token-x-details)) err-insufficient-balance)
    (asserts! (and 
      (is-none (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 get-pool-exists (contract-of token-x-trait) (contract-of token-y-trait) (get factor request-details)))
      (is-none (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 get-pool-exists (contract-of token-y-trait) (contract-of token-x-trait) (get factor request-details))))
        err-pool-exists)
    (asserts! (or (is-eq (get lock request-details) LOCK) (is-eq (get lock request-details) BURN) (is-eq (get lock request-details) NONE)) err-invalid-lock-parameter)
		(ok true)))

(define-private (post-check
	(request-details {
		token-x-trait: <ft-trait>, token-y-trait: <ft-trait>,
		factor: uint,
		bal-x: uint, bal-y: uint,
		fee-rate-x: uint, fee-rate-y: uint,
		max-in-ratio: uint, max-out-ratio: uint,
		threshold-x: uint, threshold-y: uint,
		oracle-enabled: bool, oracle-average: uint,
		start-block: uint,
		lock: (buff 1) }))
	(let (
			(token-x-trait (get token-x-trait request-details))
			(token-y-trait (get token-y-trait request-details))
			(token-x (contract-of token-x-trait))
			(token-y (contract-of token-y-trait))
			(factor (get factor request-details))
			(supply (get supply (try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 create-pool token-x-trait token-y-trait factor tx-sender (get bal-x request-details) (get bal-y request-details)))))
			(pool-id (get pool-id (try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 get-pool-details token-x token-y factor)))))						
		(and (is-eq (get lock request-details) LOCK) (try! (lock-liquidity supply pool-id)))
		(and (is-eq (get lock request-details) BURN) (try! (burn-liquidity supply pool-id)))
		(try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 set-fee-rate-x token-x token-y factor (get fee-rate-x request-details)))
		(try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 set-fee-rate-y token-x token-y factor (get fee-rate-y request-details)))
		(try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 set-max-in-ratio token-x token-y factor (get max-in-ratio request-details)))
		(try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 set-max-out-ratio token-x token-y factor (get max-out-ratio request-details)))
		(try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 set-threshold-x token-x token-y factor (get threshold-x request-details)))
		(try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 set-threshold-y token-x token-y factor (get threshold-y request-details)))
		(try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 set-oracle-enabled token-x token-y factor (get oracle-enabled request-details)))
		(try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 set-oracle-average token-x token-y factor (get oracle-average request-details)))
		(try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 set-start-block token-x token-y factor (get start-block request-details)))
		(try! (contract-call? 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-registry-v2-01 set-fee-rebate token-x token-y factor (var-get fee-rebate)))
    (ok pool-id)))	

(define-constant C32 "0123456789ABCDEFGHJKMNPQRSTVWXYZ")
(define-constant LIST_15 (list 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
(define-constant LIST_24 (list 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
(define-constant LIST_39 (concat LIST_24 LIST_15))

(define-private (c32-to-string-iter (idx int) (it { s: (string-ascii 39), r: uint }))
	{ s: (unwrap-panic (as-max-len? (concat (unwrap-panic (element-at? C32 (mod (get r it) u32))) (get s it)) u39)), r: (/ (get r it) u32) })

(define-private (hash-bytes-to-string (data (buff 24)))
	(let (
			;; fixed-length: 8 * 15 / 5 = 24
			(low-part (get s (fold c32-to-string-iter LIST_24 { s: "", r: (buff-to-uint-be (unwrap-panic (as-max-len? (unwrap-panic (slice? data u9 u24)) u16)))})))
			;; fixed-length: ceil(8 * 9 / 5) = 15
			(high-part (get s (fold c32-to-string-iter LIST_15 { s: "", r: (buff-to-uint-be (unwrap-panic (as-max-len? (unwrap-panic (slice? data u0 u9)) u16)))}))))
		(unwrap-panic (as-max-len? (concat high-part low-part) u39))))

(define-private (trim-leading-0-iter (idx int) (it (string-ascii 39)))
	(if (is-eq (element-at? it u0) (some "0")) (unwrap-panic (slice? it u1 (len it))) it))

(define-private (trim-leading-0 (s (string-ascii 39)))
	(fold trim-leading-0-iter LIST_39 s))

(define-private (append-leading-0-iter (idx int) (it { hash-bytes: (buff 24), address: (string-ascii 39)}))
	(if (is-eq (element-at? (get hash-bytes it) u0) (some 0x00))
		{ hash-bytes: (unwrap-panic (slice? (get hash-bytes it) u1 (len (get hash-bytes it)))), address: (unwrap-panic (as-max-len? (concat "0" (get address it)) u39)) }
		it))

(define-private (append-leading-0 (hash-bytes (buff 24)) (s (string-ascii 39)))
	(get address (fold append-leading-0-iter LIST_24 { hash-bytes: hash-bytes, address: s })))

(define-private (join-template-parts-iter (part (string-ascii 5000)) (state {token: (string-ascii 100), result: (string-ascii 10000)}))
  {
    token: (get token state),
    result: (unwrap-panic (as-max-len? 
      (concat 
        (get result state) 
        (if (is-eq (len (get result state)) u0) part (concat (get token state) part)))
      u10000))
  }) 

Functions (24)

FunctionAccessArgs
contract-name-length-byteread-onlylength: uint
contract-code-length-length-bytesread-onlylength: uint
string-to-buffread-onlystr: (string-ascii 80
calculate-txidread-onlynonce: (buff 8
is-dao-or-extensionread-only
get-approved-token-x-or-defaultread-onlytoken-x: principal
get-fee-rebateread-only
get-lock-periodread-only
get-locked-liquidity-or-defaultread-onlyowner: principal, pool-id: uint
get-locked-liquidity-for-pool-or-defaultread-onlypool-id: uint
get-burnt-liquidity-or-defaultread-onlypool-id: uint
get-wrapped-token-contract-coderead-onlytoken: principal
principal-to-stringread-onlyp: principal
lock-liquiditypublicamount: uint, pool-id: uint
burn-liquiditypublicamount: uint, pool-id: uint
claim-liquiditypublicpool-id: uint
approve-token-xpublictoken: principal, approved: bool, min-x: uint
set-fee-rebatepublicnew-fee-rebate: uint
set-wrapped-token-templatepublicnew-template: (list 20 (string-ascii 5000
hash-bytes-to-stringprivatedata: (buff 24
trim-leading-0-iterprivateidx: int, it: (string-ascii 39
trim-leading-0privates: (string-ascii 39
append-leading-0privatehash-bytes: (buff 24
join-template-parts-iterprivatepart: (string-ascii 5000