Source Code



;; SIP009 interface (testnet)
;;(impl-trait 'ST32XCD69XPS3GKDEXAQ29PJRDSD5AR643GY0C3Q5.nft-trait.nft-trait)
 
;; SIP009 interface (mainnet)
;;(impl-trait 'SP39EMTZG4P7D55FMEQXEB8ZEQEK0ECBHB1GD8GMT.nft-trait.nft-trait)

;; Constants
;;
(define-constant CONTRACT_OWNER tx-sender)
(define-constant VERSION "v0.1") ;; version string
(define-constant MANPAGE "https://www.bitfari.org/man/simple-ad-v01") ;; smart contract manual

;; Errors
;; 

(define-constant ERR_NOT_AUTHORIZED (err u401)) ;;: not authorized for the operation
(define-constant ERR_ALREADY_MINTED (err u402)) ;;: already registered
(define-constant ERR_CANT_MINT (err u403)) ;;:::::: minting not authorized, possible limit reached
(define-constant ERR_NOT_FOUND (err u404)) ;;:::::: no ad map entry
(define-constant ERR_STX_TRANSFER (err u405)) ;;::: non-sponsored purchase
(define-constant ERR_PRICE_TOO_LOW (err u406)) ;;:: tried to submit a tx with low or no fee
(define-constant ERR_PAYMENT_FAILURE (err u407)) ;; minting not paid
(define-constant ERR_SENDING_PAYMENT (err u408)) ;; error while sending the payment 
(define-constant ERR_CANT_MAP_AD (err u411)) ;;: map insert error, check data
(define-constant ERR_INVALID_ID (err u414)) ;;::::: invalid nft id 
(define-constant ERR_TOKEN_TRANSFER (err u415)) ;;: failure during a token transfer operation

;; Vars
;;

(define-data-var last-id        uint u0)
(define-data-var fees           uint u2000)      ;;: ad registration fees
(define-data-var min-usd        uint u1)            ;;: default price in USD
(define-data-var min-fari       uint u140000) ;;: default price in FARI
(define-data-var min-stx        uint u450)    ;;: default price in STX
(define-data-var json-root      (string-ascii 256) "https://api.bitfari.com/json/ads/")
(define-data-var ipfs-root      (string-ascii 256) "https://ipfs.io/Yh3erdsj/ads/")

;; Register a simple ad nft
;;
(define-non-fungible-token simple-ad uint)

;; Simple ad standard definition  
(define-map ads { id: uint } {
advertiser: principal, json: (string-ascii 256),
;; The address of the network, for example
;; classifieds.btc, billboards.btc, or malls.btc 
network: principal, title: (string-ascii 256), copy: (string-ascii 512),

;; One url for the art image itself, 
;; another for a desired customer landing page 
;; This even helps billboard ads, as customers see
;; a double feature of the ad on their wallets
art-url:  (string-ascii 256), click-url: (string-ascii 256),

;; Aural links help the visually tourists, immigrants 
;; and the visually impaired navigate cities better
aural-url: (string-ascii 256), 

;; Action links help assess the success of CPA campaigns
;; while the action code helps monitor sucess/error conditions
action-url: (string-ascii 256), action-code: (string-ascii 64), 

;; Campaign Targeting Settings
;; demographic targeting 
demo: (string-ascii 256),  
;; ethnographic targeting 
ethno: (string-ascii 256), 
;; psycographic targeting
psycho: (string-ascii 256),  
;; behavioral targeting
behavioral: (string-ascii 256),
;; crypto targeting, ownership of tokens, balances, etc
crypto: (string-ascii 256), 

;; future targeting -- ad holder for future modalities
;; this field is a json
future: (string-ascii 256),

;; geotargeting

;; geolocations is an array of Open Stree Map 
;; locations where the ad will be distributed
;; this field is a json
geolocations: (string-ascii 256),

;; geosettings
osm-id: uint, osm-type: (string-ascii 32),
radius: uint,   

;; ad booking

;; booking for real screens/billboards
;; array of Bitfari screens
;; where the ad will be distributed
;; this field is a json pointing to a 
;; collection of screens/billboards
screens: (string-ascii 256),

;; auditing
audited: bool, red-flag: bool, 

;; budget and payments
;; The remaining budget is updated daily
;; and is used for distributed booking in the case
;; of an ad-serving screen disconnection
budget: uint,

;; Promote ad showing to certain groups  
;; and remove the ads for certain keywords. (+ positive keywords, - negative keywords)
;; Please note that these are billboard/smart screen ads,
;; online ads have many more filtering/targeting options
keywords: (string-ascii 1024) })

;; A campaign might run for as little as 
;; one hour or years if the advertiser wishes 
(define-map schedules { ad-id: uint } 
                      { start-date: (string-ascii 16), start-time: (string-ascii 16)
                      , end-date: (string-ascii 16), end-time: (string-ascii 16)})
 
;; SIP009

;; SIP009: Transfer the token to a specified principal
 (define-public (transfer (ad-id uint) (owner principal) (recipient principal))
  (if
    (and 
      (is-eq (some tx-sender) (nft-get-owner? simple-ad ad-id))
      (is-eq owner tx-sender))
    (map-transfer ad-id owner recipient)
    ERR_TOKEN_TRANSFER))

;; SIP009: Get the owner of the specified token ID
 (define-read-only (get-owner (id uint))
   (ok (nft-get-owner? simple-ad id)))
 
;; SIP009: Get the last token ID
 (define-read-only (get-last-ad-id)
   (ok (var-get last-id)))

;; SIP009: Get the token URI
 (define-read-only (get-token-uri (ad-id uint))
  (ok (get json (map-get? ads {id: ad-id}))))

;; Private functions
;;
(define-private (is-owner (id uint) (advertiser principal))
   (is-eq advertiser (unwrap! (nft-get-owner? simple-ad id) false)))

;; Insert maps 
;;

;; Inserts a new ad entry
;; @returns bool 
(define-public (insert-ad (id uint)  
                (advertiser principal) (json (string-ascii 256)) 
                (network principal) (title (string-ascii 256)) 
                (copy (string-ascii 512)) (art-url (string-ascii 256))  
                (click-url (string-ascii 256)) (aural-url (string-ascii 256))
                (audited bool) (red-flag bool) (geolocations (string-ascii 256))
                (osm-id uint) (osm-type (string-ascii 32)) (radius uint)
                (action-url (string-ascii 256)) (action-code (string-ascii 64))
                (demo (string-ascii 256)) (behavioral (string-ascii 256)) 
                (ethno (string-ascii 256)) (psycho (string-ascii 256)) (crypto (string-ascii 256)) 
                (future (string-ascii 256)) (screens (string-ascii 256)) (budget uint) 
                (keywords (string-ascii 1024)))

    (begin

    (asserts! (is-eq contract-caller CONTRACT_OWNER) ERR_NOT_AUTHORIZED)
 
    (ok (map-insert ads 
            { id: id }
            { advertiser: advertiser , json: json, 
            network: network, title: title, copy: copy,
            art-url: art-url, click-url: click-url,
            aural-url: aural-url, audited: audited, red-flag: red-flag,
            geolocations: geolocations, osm-id: osm-id, osm-type: osm-type, radius: radius,
            action-url: action-url, action-code: action-code, demo: demo, behavioral: behavioral,
            ethno: ethno, psycho: psycho, future: future, crypto: crypto,
            screens: screens, budget: budget, keywords: keywords }))))

;; Read-only functions
;;

;; Returns version 
;; of the smart contract
;; @returns string-ascii
(define-read-only (get-version) 
    VERSION)

;; Returns the smart contract 
;; manpage - a manual
;; @returns url/string-ascii
(define-read-only (get-man) 
    MANPAGE)

;;  Getters
;; ------------------------------------------------------------------------------------------------------------------

;; Get the last token ID
(define-read-only (get-ad-id)
  (ok (var-get last-id)))

;; Get the advertiser associated with this ad
(define-read-only (get-advertiser (id uint))
  (default-to CONTRACT_OWNER (get advertiser (map-get? ads { id: id }))))

;; Get the title associated with this ad
(define-read-only (get-title (id uint))
   (default-to "" (get title (map-get? ads { id: id }))))

;; Get the copy associated with this ad
(define-read-only (get-copy (id uint))
   (default-to "" (get copy (map-get? ads { id: id }))))

;; Get the network associated with this ad
(define-read-only (get-network (id uint))
   (default-to CONTRACT_OWNER (get network (map-get? ads { id: id }))))

;; Get red flags, if any
(define-read-only (get-red-flag (id uint))
   (default-to false (get red-flag (map-get? ads { id: id }))))

;; Get audited status 
(define-read-only (get-audited (id uint))
   (default-to false (get audited (map-get? ads { id: id }))))

;; Get osm id, if applicable
(define-read-only (get-osm-id (id uint))
   (default-to u0 (get osm-id (map-get? ads { id: id }))))  

;; Get osm type, if applicable
(define-read-only (get-osm-type (id uint))
   (default-to "way" (get osm-type (map-get? ads { id: id }))))  

;; Get radius, if applicable
(define-read-only (get-radius (id uint))
   (default-to u1 (get radius (map-get? ads { id: id }))))  

;; Get action url, if applicable
(define-read-only (get-action-url (id uint))
   (default-to "" (get action-url (map-get? ads { id: id }))))  

;; Get action code, if applicable
(define-read-only (get-action-code (id uint))
   (default-to "" (get action-code (map-get? ads { id: id })))) 

;; Get demographic information
(define-read-only (get-demo (id uint))
   (default-to "" (get demo (map-get? ads { id: id }))))  

;; Get behavioral information
(define-read-only (get-behavioral (id uint))
   (default-to "" (get behavioral (map-get? ads { id: id }))))  

;; Get crypto information
(define-read-only (get-crypto (id uint))
   (default-to "" (get crypto (map-get? ads { id: id }))))  

;; Get ethnographic information
(define-read-only (get-ethno (id uint))
   (default-to "" (get ethno (map-get? ads { id: id }))))  

;; Get psychographic information
(define-read-only (get-psycho (id uint))
   (default-to "" (get psycho (map-get? ads { id: id }))))  

;; Get future targeting parameters
(define-read-only (get-future (id uint))
   (default-to "" (get future (map-get? ads { id: id }))))  

;; Get booked screens/billboards
(define-read-only (get-screens (id uint))
   (default-to "" (get screens (map-get? ads { id: id }))))   

;; Get the running times associated with this ad
(define-read-only (day-starts (id uint))
  (default-to "" (get start-date (map-get? schedules { ad-id: id }))))
 
(define-read-only (time-starts (id uint))
  (default-to "" (get start-time (map-get? schedules { ad-id: id }))))

;; Get the finishing times associated with this ad
(define-read-only (day-ends (id uint))
  (default-to "" (get end-date (map-get? schedules { ad-id: id }))))
 
(define-read-only (time-ends (id uint))
  (default-to "" (get end-time (map-get? schedules { ad-id: id }))))

;; Get the art-url associated with this campaign
;; only accepts links from whitelisted sources or ipfs 
(define-read-only (get-art-url (id uint))
  (default-to "" (get art-url (map-get? ads { id: id }))))

;; Get the click-url associated with this campaign
(define-read-only (get-click-url (id uint))
  (default-to "" (get click-url (map-get? ads { id: id }))))

;; Get the aural-url associated with this campaign
(define-read-only (get-aural-url (id uint))
  (default-to "" (get aural-url (map-get? ads { id: id }))))
 
;; Get the budget associated with this campaign
(define-read-only (get-budget (id uint))
  (default-to u0 (get budget (map-get? ads { id: id }))))

;; Get Ad Geolocations
;; @returns string-ascii/url 
(define-read-only (get-geolocations (id uint)  )
  (default-to "" (get geolocations (map-get? ads {id: id }))))

;; Get the positive and negative keywords associated with this campaign
(define-read-only (get-keywords (id uint))
  (default-to "" (get keywords (map-get? ads { id: id }))))

;; Get min fees 
;; Returns minimum fees
;; @returns uint 
(define-read-only (get-fees)
    (var-get fees))

;; Get ad details 
;; @returns map 
(define-read-only (get-ad (id uint))
  (map-get? ads {id: id}))

;; Asset specific public functions
;;

;; Set fees 
;; Returns minimum fees
;; @returns uint 
(define-public (set-fees (new-fee uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) (err ERR_NOT_AUTHORIZED))
    (ok (var-set fees new-fee))))  

;; Roots
;; ads can be minted to the Bitfari API
;; or to IPFS for full decentralization

;; Set root 
;; Update NFT root dir for jsons
;; @returns uint 
(define-public (set-root (new-root (string-ascii 256)))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) (err ERR_NOT_AUTHORIZED))
    (ok (var-set json-root new-root))))  

;; Set ipfs/web3 root 
;; Update NFT ipfs root dir for jsons
;; @returns uint 
(define-public (set-ipfs-root (new-ipfs (string-ascii 256)))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) (err ERR_NOT_AUTHORIZED))
    (ok (var-set ipfs-root new-ipfs))))  

;; Updating ads
;;

;; Updates an ad
;; @returns bool 
(define-public (update-ad (id uint)  
                (id uint)  
                (advertiser principal) (json (string-ascii 256)) 
                (network principal) (title (string-ascii 256)) 
                (copy (string-ascii 512)) (art-url (string-ascii 256))  
                (click-url (string-ascii 256)) (aural-url (string-ascii 256))
                (audited bool) (red-flag bool) (geolocations (string-ascii 256))
                (osm-id uint) (osm-type (string-ascii 32)) (radius uint)
                (action-url (string-ascii 256)) (action-code (string-ascii 64))
                (demo (string-ascii 256)) (behavioral (string-ascii 256)) 
                (ethno (string-ascii 256)) (psycho (string-ascii 256)) (crypto (string-ascii 256)) 
                (future (string-ascii 256)) (screens (string-ascii 256)) (budget uint) 
                (keywords (string-ascii 1024)))
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) (err ERR_NOT_AUTHORIZED))
        (ok (map-set ads { id: id }
            { advertiser: advertiser, json: json, 
            network: network, title: title, 
            art-url: art-url, behavioral: behavioral, budget: budget, click-url: click-url,
            copy: copy, crypto: crypto, demo: demo, ethno: ethno, future: future, psycho: psycho,
            aural-url: aural-url, audited: audited, red-flag: red-flag,
            geolocations: geolocations, osm-id: osm-id, osm-type: osm-type, radius: radius,
            action-url: action-url, action-code: action-code,   
            screens: screens,  keywords: keywords }))))

;; Transfers an ad
;; @returns bool 

(define-private (map-transfer (ad-id uint) (owner principal) (recipient principal))
(begin
    (map-set ads { id: ad-id }
          { advertiser: recipient, json: (get-json ad-id), 
            network: (get-network ad-id), title: (get-title ad-id), copy: (get-copy ad-id),
            art-url: (get-art-url ad-id), click-url: (get-click-url ad-id),
            aural-url: (get-aural-url ad-id), audited: (get-audited ad-id), red-flag: (get-red-flag ad-id),
            geolocations: (get-geolocations ad-id), osm-id: (get-osm-id ad-id),
            osm-type: (get-osm-type ad-id), radius: (get-radius ad-id),
            action-url: (get-action-url ad-id), action-code: (get-action-code ad-id), demo: (get-demo ad-id),
            ethno: (get-ethno ad-id), psycho: (get-psycho ad-id), future: (get-future ad-id),
            crypto: (get-crypto ad-id), behavioral: (get-behavioral ad-id),
            screens: (get-screens ad-id), budget: (get-budget ad-id), keywords: (get-keywords ad-id)})
   (nft-transfer? simple-ad ad-id owner recipient)))

;; JSON
;; ;; @returns json/string ascii 256
(define-read-only (get-json (ad-id uint))
  (default-to "none" (get json (map-get? ads { id: ad-id }))))

;; BURNING AND DELETING
;;
;; Burn deletes a token, removing it from the wallet
;; this is non-reversible

;; Three map delete utilities are provided to remove
;; orphan records created by token burning operations.

;; These functions are meant to be executed in tandem
;; by an authorized client.

;; Burns a ad - in case of mistakes, 
;; privacy requests, etc
;; @returns bool or err
(define-public (burn (ad-id uint))
  (begin
    (if (is-owner ad-id tx-sender)
      (match (nft-burn? simple-ad ad-id tx-sender)
        success (ok true)
        error (err error)) ERR_NOT_AUTHORIZED)))

;; Delete a listing in the ad map
;; @returns bool 
(define-public (delete-ad (id uint)  )
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) (err ERR_NOT_AUTHORIZED))
        (ok (map-delete ads { id: id }))))

;; Get Roots

;; Web3 IPFS
;; @returns string-ascii 
(define-read-only (get-ipfs-root)
  (var-get ipfs-root))
 
;; Web2 JSON
;; @returns string-ascii 
(define-read-only (get-json-root)
  (var-get json-root))

;; Minting

;; Mints an ad
;; @returns bool 
 (define-private (mint 
                (id uint)  
                (advertiser principal) (json (string-ascii 256)) 
                (network principal) (title (string-ascii 256)) 
                (copy (string-ascii 512)) (art-url (string-ascii 256))  
                (click-url (string-ascii 256)) (aural-url (string-ascii 256))
                (audited bool) (red-flag bool) (geolocations (string-ascii 256))
                (osm-id uint) (osm-type (string-ascii 32)) (radius uint)
                (action-url (string-ascii 256)) (action-code (string-ascii 64))
                (demo (string-ascii 256)) (behavioral (string-ascii 256)) 
                (ethno (string-ascii 256)) (psycho (string-ascii 256)) (crypto (string-ascii 256)) 
                (future (string-ascii 256)) (screens (string-ascii 256)) (budget uint) 
                (keywords (string-ascii 1024)))
 
        (let ((next-id (+ u1 (var-get last-id))))

        ;; Check osm id + type is not registered
        (asserts! (is-none (map-get? ads {id: id })) ERR_ALREADY_MINTED)

        ;; To avoid double entries, keep straightforward ownership records, etc.
        (asserts! (map-insert ads 
            { id: id } 
            { advertiser: advertiser , json: json, 
            network: network, title: title, copy: copy,
            art-url: art-url, click-url: click-url,
            aural-url: aural-url, audited: audited, red-flag: red-flag,
            geolocations: geolocations, osm-id: osm-id, osm-type: osm-type, radius: radius,
            action-url: action-url, action-code: action-code, demo: demo, behavioral: behavioral,
            ethno: ethno, psycho: psycho, future: future, crypto: crypto,
            screens: screens, budget: budget, keywords: keywords }) ERR_CANT_MAP_AD)
 
        ;; Finally, mint after asserts and children record creation
        ;; a new id is assigned to this token
        (match (nft-mint? simple-ad next-id advertiser)
            success
            (begin
            (var-set last-id next-id)       
            (ok true))
            error (err error))))
  
;; ;; Book Ad
;; ;; @returns bool 
(define-public (publish-ad (id uint) (amount-stx uint) 
                (advertiser principal) (json (string-ascii 256)) 
                (network principal) (title (string-ascii 256)) 
                (copy (string-ascii 512)) (art-url (string-ascii 256))  
                (click-url (string-ascii 256)) (aural-url (string-ascii 256))
                (audited bool) (red-flag bool) (geolocations (string-ascii 256))
                (osm-id uint) (osm-type (string-ascii 32)) (radius uint)
                (action-url (string-ascii 256)) (action-code (string-ascii 64))
                (demo (string-ascii 256)) (behavioral (string-ascii 256)) 
                (ethno (string-ascii 256)) (psycho (string-ascii 256)) (crypto (string-ascii 256)) 
                (future (string-ascii 256)) (screens (string-ascii 256)) (budget uint) 
                (keywords (string-ascii 1024)))
  (if ( >= amount-stx (var-get min-stx))
  (begin    
  (try! (stx-transfer? amount-stx tx-sender CONTRACT_OWNER))
  (try! (as-contract (mint id advertiser json network title   
                            copy art-url click-url aural-url  
                            audited red-flag geolocations  
                            osm-id osm-type radius
                            action-url action-code demo   
                            behavioral ethno psycho crypto 
                            future screens budget keywords )))
  (ok true)) ERR_PRICE_TOO_LOW))

Functions (49)

FunctionAccessArgs
transferpublicad-id: uint, owner: principal, recipient: principal
get-ownerread-onlyid: uint
get-last-ad-idread-only
get-token-uriread-onlyad-id: uint
is-ownerprivateid: uint, advertiser: principal
insert-adpublicid: uint, advertiser: principal, json: (string-ascii 256
get-versionread-only
get-manread-only
get-ad-idread-only
get-advertiserread-onlyid: uint
get-titleread-onlyid: uint
get-copyread-onlyid: uint
get-networkread-onlyid: uint
get-red-flagread-onlyid: uint
get-auditedread-onlyid: uint
get-osm-idread-onlyid: uint
get-osm-typeread-onlyid: uint
get-radiusread-onlyid: uint
get-action-urlread-onlyid: uint
get-action-coderead-onlyid: uint
get-demoread-onlyid: uint
get-behavioralread-onlyid: uint
get-cryptoread-onlyid: uint
get-ethnoread-onlyid: uint
get-psychoread-onlyid: uint
get-futureread-onlyid: uint
get-screensread-onlyid: uint
day-startsread-onlyid: uint
time-startsread-onlyid: uint
day-endsread-onlyid: uint
time-endsread-onlyid: uint
get-art-urlread-onlyid: uint
get-click-urlread-onlyid: uint
get-aural-urlread-onlyid: uint
get-budgetread-onlyid: uint
get-keywordsread-onlyid: uint
get-feesread-only
get-adread-onlyid: uint
set-feespublicnew-fee: uint
set-rootpublicnew-root: (string-ascii 256
set-ipfs-rootpublicnew-ipfs: (string-ascii 256
update-adpublicid: uint, id: uint, advertiser: principal, json: (string-ascii 256
map-transferprivatead-id: uint, owner: principal, recipient: principal
get-jsonread-onlyad-id: uint
burnpublicad-id: uint
get-ipfs-rootread-only
get-json-rootread-only
mintprivateid: uint, advertiser: principal, json: (string-ascii 256
publish-adpublicid: uint, amount-stx: uint, advertiser: principal, json: (string-ascii 256