├── .github ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── poetry.lock ├── pyproject.toml ├── renovate.json └── src ├── __init__.py ├── actions.py ├── algorealm.py ├── const.py ├── contracts ├── README.md ├── algorealm_approval.teal ├── algorealm_clear.teal ├── algorealm_law.teal ├── card │ ├── README.md │ └── algorealm_card_contract.teal └── tmpl_algorealm_law.teal └── query.py /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | > _Please explain the changes you made here_ 4 | 5 | ### Checklist 6 | 7 | > _Please, make sure to comply with the checklist below before expecting review_ 8 | 9 | - [ ] Code compiles correctly 10 | - [ ] Created tests which fail without the change (if possible) 11 | - [ ] All tests passing 12 | - [ ] Extended the README / documentation, if necessary 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '*' 7 | push: 8 | branches: 9 | - 'main' 10 | 11 | jobs: 12 | run-ci: 13 | 14 | name: Run Type Check & Linters 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v4 22 | with: 23 | python-version: 3.10.8 24 | 25 | - name: Set up Poetry 26 | uses: abatilo/actions-poetry@v2.2.0 27 | with: 28 | poetry-version: 1.1.13 29 | 30 | - name: Install python dependencies 31 | run: poetry install 32 | 33 | - uses: pre-commit/action@v3.0.0 34 | name: "Linters and formatters check" 35 | with: 36 | extra_args: --all-files --show-diff-on-failure 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # AlgoRealm artifacts 132 | *.gtxn 133 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.1.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | 9 | - repo: https://github.com/psf/black 10 | rev: 22.6.0 11 | hooks: 12 | - id: black 13 | 14 | - repo: https://github.com/pycqa/isort 15 | rev: 5.10.1 16 | hooks: 17 | - id: isort 18 | args: ["--profile", "black"] 19 | 20 | - repo: https://github.com/myint/autoflake 21 | rev: v1.4 22 | hooks: 23 | - id: autoflake 24 | args: 25 | - --in-place 26 | - --remove-unused-variables 27 | - --remove-all-unused-imports 28 | - --expand-star-imports 29 | - --ignore-init-module-imports 30 | 31 | - repo: https://github.com/myint/eradicate 32 | rev: v2.0.0 33 | hooks: 34 | - id: eradicate 35 | args: 36 | - --in-place 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Cosimo Bassi (aka cusma) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | 6 | 7 |

8 | 9 | ## Incipit 10 | 11 | ``` 12 | There was a time 13 | When nothing but Entropy was there. 14 | Then came the cryptographic Proof, 15 | And took it care. 16 | 17 | Verifiability of Randomness, 18 | Since genesis block, 19 | Brings Consensus over realm vastness, 20 | So Algorand shall not fork. 21 | ``` 22 | 23 | ## Become a Majesty of Algorand 24 | 25 | Only generous hearts will rule over Algorand realm. 26 | 27 | Show how generous is your heart donating some ALGOs to the [Rewards Pool](https://developer.algorand.org/docs/reference/algorand-networks/mainnet/#rewardspool-address) and claim the title of **Randomic Majesty of Algorand** or **Verifiable Majesty of Algorand**. 28 | 29 | The more generous you are, the harder will be to be dethroned. 30 | 31 | Join [AlgoRealm channel](https://t.me/algorealm)! 32 | 33 | ## Play with AlgoRealm CLI Web Emulator 34 | 35 | Play [AlgoRealm on CLI web emulator](https://algorealm.vercel.app/console) by [@aorumbayev](https://github.com/aorumbayev). 36 | 37 | ## Play with AlgoRealm CLI 38 | 39 | 40 | ### 0. Prerequisites 41 | 42 | - [poetry](https://python-poetry.org/) 43 | - [python >= 3.10](https://www.python.org/) 44 | 45 | ### 1. Setup 46 | 47 | ```shell 48 | $ poetry install # install dependencies 49 | $ poetry shell # activate virtual environment 50 | $ cd src # cd into source directory, to be replaced with UI webapp in future 51 | ``` 52 | 53 | ### 2. How to play 54 | 55 | Playing **AlgoRealm** from your CLI is pretty easy, just ask for help: 56 | 57 | ```shell 58 | $ python3 algorealm.py -h 59 | ``` 60 | 61 | ```shell 62 | AlgoRealm, only generous heart will ever rule over Algorand. (by cusma) 63 | 64 | Usage: 65 | algorealm.py poem 66 | algorealm.py dynasty [--test] 67 | algorealm.py longevity (--crown | --sceptre) [--test] 68 | algorealm.py braveness (--crown | --sceptre) [--test] 69 | algorealm.py claim-majesty (--crown | --sceptre) [--test] 70 | algorealm.py claim-card 71 | algorealm.py buy-order [--notify] 72 | algorealm.py verify-order 73 | algorealm.py sell-card 74 | algorealm.py [--help] 75 | 76 | Commands: 77 | poem AlgoRealm's poem. 78 | dynasty Print the glorious dynasty of AlgoRealm's Majesties. 79 | longevity Print AlgoRealm's Majesties longevity. 80 | braveness Print AlgoRealm's Majesties braveness. 81 | claim-majesty Claim the Crown of Entropy or the Sceptre of Proof, become Majesty of Algorand. 82 | claim-card Brake the spell and claim the AlgoRealm Card by AlgoWorld. 83 | buy-order Place an order for the AlgoRealm Card. 84 | verify-order Verify the partially signed AlgoRealm Card buy order. 85 | sell-card Sell the AlgoRealm Card (paying a 10% royalty). 86 | 87 | Options: 88 | -n, --notify Notify the Seller about your buy order on-chain. 89 | -t, --test TestNet mode 90 | -h, --help 91 | ``` 92 | 93 | ⚠️ Keep your `` safe! Although you will only use it on you local machine, is it strongly recommended to make use of a dedicated account just to play AlgoRealm! 94 | 95 | > In case you want to give a try, you can play AlgoRealm on TestNet adding `-t` 96 | > to CLI commands. 97 | 98 | ### 3. AlgoRealm Dynasty, Longevity and Braveness 99 | 100 | Who are the Majesties of the Algorand realm? 101 | 102 | 1. Discover it directly on [Algorand blockchain](https://algoexplorer.io/application/137491307) 103 | 104 | 2. Discover it with your node: 105 | ```shell 106 | $ ./goal app read --app-id 137491307 --global 107 | ``` 108 | 109 | 3. Discover it with the AlgoRealm CLI: 110 | ```shell 111 | $ python3 algorealm.py dynasty 112 | ``` 113 | 114 | ``` 115 | __ __ ___ __ __ 116 | \*) \*) \*/ (*/ (*/ 117 | \*\_\*\_|O|_/*/_/*/ 118 | \_______________/ 119 | _ __ _______ __ 120 | / \ [ | |_ __ \ [ | 121 | / _ \ | | .--./) .--. | |__) | .---. ,--. | | _ .--..--. 122 | / ___ \ | | / /'`\;/ .'`\ \ | __ / / /__\\`'_\ : | | [ `.-. .-. | 123 | _/ / \ \_ | | \ \._//| \__. |_| | \ \_| \__.,// | |, | | | | | | | | 124 | |____| |____|[___].',__` '.__.'|____| |___|'.__.'\'-;__/[___][___||__||__] 125 | ( ( __)) 126 | *** DYNASTY *** 127 | 128 | 129 | 👑 jkbishbish claimed the Crown of Entropy 130 | on Block: 13578171 donating: 2 microALGOs to the Rewards Pool. 131 | 132 | 🪄 jkbishbish claimed the Sceptre of Proof 133 | on Block: 13578330 donating: 2 microALGOs to the Rewards Pool. 134 | 135 | 👑 tmc claimed the Crown of Entropy 136 | on Block: 14936018 donating: 3 microALGOs to the Rewards Pool. 137 | 138 | 🪄 tmc claimed the Sceptre of Proof 139 | on Block: 14936235 donating: 3 microALGOs to the Rewards Pool. 140 | 141 | 👑 nullun claimed the Crown of Entropy 142 | on Block: 14989913 donating: 4 microALGOs to the Rewards Pool. 143 | 144 | 🪄 nullun claimed the Sceptre of Proof 145 | on Block: 14989913 donating: 4 microALGOs to the Rewards Pool. 146 | ``` 147 | 148 | 4. Which was the longest lasting Majesty? 149 | ```shell 150 | $ python3 algorealm.py longevity --crown 151 | ``` 152 | 153 | ``` 154 | *** 👑 RANDOMIC MAJESTY LONGEVITY *** 155 | 156 | +--------------------+--------------------+ 157 | | Majesty Name | Longevity (blocks) | 158 | +--------------------+--------------------+ 159 | | MillionAlgosFather | 5768768 | 160 | | nullun | 3366046 | 161 | | jkbishbish | 1357847 | 162 | | Matt | 1248429 | 163 | | renangeo | 416539 | 164 | | 👑🅿️ | 158346 | 165 | | tmc | 53895 | 166 | | MillionAlgosFather | 32978 | 167 | | nullun | 3369 | 168 | +--------------------+--------------------+ 169 | ``` 170 | 171 | 5. Who is the bravest Majesty of all time? 172 | ```shell 173 | $ python3 algorealm.py braveness --crown 174 | ``` 175 | 176 | ``` 177 | *** 👑 RANDOMIC MAJESTY BRAVENESS *** 178 | 179 | +--------------------+-----------+ 180 | | Majesty Name | Braveness | 181 | +--------------------+-----------+ 182 | | renangeo | 7.824 | 183 | | MillionAlgosFather | 4.605 | 184 | | 👑🅿️ | 1.609 | 185 | | jkbishbish | 1 | 186 | | tmc | 0.405 | 187 | | nullun | 0.288 | 188 | | nullun | 0.0 | 189 | | MillionAlgosFather | 0.0 | 190 | | Matt | 0.0 | 191 | +--------------------+-----------+ 192 | ``` 193 | 194 | > Braveness is based on the relative gorwth of donation amounts (`d'`, `d`): 195 | > 196 | > `braveness = ln(d') - ln(d)` 197 | 198 | ### 4. Claim the Crown of Entropy or the Sceptre of Proof 199 | 200 | Chose your `` and become part of the Dynasty! Remember that to dethrone the current Majesties you must donate to the Algorand's Rewards Pool more `` than the last donation. 201 | 202 | ```shell 203 | $ python3 claim-majesty (--crown | --sceptre) [--test] 204 | ``` 205 | 206 | ⚠️ Enter the the `mnemonic` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! 207 | 208 | ### 5. Claim the AlgoRealm Special Card 209 | 210 | The [AlgoRealm Card](https://algoexplorer.io/asset/321172366) is a unique [AlgoWorld NFT](https://algoworld.io/) Special Card, securely stored in an enchanted coffer. 211 | 212 | 213 | algorealm_card 214 | 215 | 216 | Only the generous heart of the [Great Majesty of Algorand](https://github.com/cusma/algorealm) will ever able to break the spell, claim the **unique Special Card** and trade it! So, you previously need to conquer both the [Crown of Entropy](https://github.com/cusma/algorealm#claim-the-crown-of-entropy) and the [Sceptre of Proof](https://github.com/cusma/algorealm#claim-the-sceptre-of-proof), ascending to [AlgoRealm's throne](https://algoexplorer.io/application/137491307). 217 | 218 | The AlgoRealm Card can be claimed **starting from block 16,250,000** using the command `claim-card`: hold strong both the Crown and the Sceptre and keep the throne until there! 219 | 220 | ```shell 221 | $ python3 algorealm.py claim-card [--test] 222 | ``` 223 | 224 | ⚠️ Enter the the `mnemonic` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! 225 | 226 | ### 6. Place a buy-order 227 | 228 | As a **Buyer** you can easily place a **buy-order** proposal to the **Seller** using the `buy-order` command. You just need to choose the `` amount for the buy order proposal. 229 | 230 | Using the `--notify` option the **Seller** will receive a notification on-chain, being acknowledged about the new buy-order proposal. 231 | 232 | ```shell 233 | $ python3 algorealm.py buy-order [--notify] [--test] 234 | ``` 235 | 236 | ⚠️ Enter the the `mnemonic` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! 237 | 238 | As result, a *Partially Signed Trade Group Transaction* is created as `trade.gtx` file in the `algorealm.py` CLI directory. Note that there is **no counter-party risk** in this operation: as a **Buyer** you can safely send the `trade.gtxn` file to the **Seller**, being sure that the trade will be executed **if and only if** the Seller will transfer the AlgoRealm Special Card to you. 239 | 240 | ### 7. Verify a buy-order 241 | 242 | As a **Seller** you can review and verify the buy-order proposal, validating the amounts of the trade. Place the `trade.gtxn` file, received from the **Buyer**, in the same directory of your `algorealm.py` CLI. 243 | 244 | The `verify-order` command requires your `` as argument. 245 | 246 | ```shell 247 | $ python3 algorealm.py verify-order 248 | ``` 249 | 250 | Some compliancy checks are performed over the `trade.gtx` file before displaying the buy-order summary: 251 | 252 | ```shell 253 | * =========================== ORDER SUMMARY =========================== * 254 | 255 | BUYER: 256 | SELLER: 257 | AMOUNT: 1.0 ALGO 258 | ROYALTY: 0.1 ALGO 259 | 260 | LAST VALID BLOCK: 13184621 261 | 262 | * ===================================================================== * 263 | ``` 264 | 265 | If you agree with the buy-order proposal you can sell the AlgoRealm Special Card. 266 | 267 | ### 8. Sell card 268 | 269 | As a **Seller**, if you agree with the buy-order proposal, you can sell your AlgoRealm Special Card using the command `sell-card`. 270 | 271 | ```shell 272 | $ python3 algorealm.py sell-card [--test] 273 | ``` 274 | 275 | ⚠️ Enter the the `mnemonic` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! 276 | 277 | 278 | ## Play with goal CLI 279 | 280 | AlgoRealm could also be a good challenge to [run your own Algorand node](https://developer.algorand.org/docs/run-a-node/setup/install/) and familiarise the [goal CLI commands](https://developer.algorand.org/docs/reference/cli/goal/goal/). 281 | 282 |
283 | Click to expand the guidelines! 284 | 285 | ### 1. Claim the Crown of Entropy 286 | 287 | 1. Save the [AlgoRealm Law](https://github.com/cusma/algorealm/blob/main/algorealm_law.teal) into your node directory. 288 | 2. Find out who owns the [Crown of Entropy](https://algoexplorer.io/asset/137493252) (keep the `CROWN_OWNER_ADDRESS`) and Opt-In. 289 | 290 | ```bash 291 | $ ./goal asset send -f YOUR_ADDRESS -t YOUR_ADDRESS --assetid 137493252 -a 0 292 | ``` 293 | 294 | 3. Write the unsigned `crown_claim.txn` Applicarion Call transaction passing `"str:YOUR_NAME"` as `--app-arg`. 295 | 296 | ```bash 297 | $ ./goal app call --app-id 137491307 -f YOUR_ADDRESS --app-arg "str:Crown" --app-arg "str:YOUR_NAME" -o crown_claim.txn 298 | ``` 299 | 300 | 4. Write the unsigned `crown_donation.txn` Payment transaction to the Rewards Pool specifying `YOUR_DONATION` in microALGOs. The claim will be successful if `YOUR_DONATION` is grater than the current one. 301 | 302 | ```bash 303 | $ ./goal clerk send -f YOUR_ADDRESS -t 737777777777777777777777777777777777777777777777777UFEJ2CI -a YOUR_DONATION -o crown_donation.txn 304 | ``` 305 | 306 | 5. Write the unsigned `crown_transfer.txn` Asset Transfer transaction form `CROWN_OWNER_ADDRESS` to `YOUR_ADDRESS`. 307 | 308 | ```bash 309 | $ ./goal asset send -f CROWN_OWNER_ADDRESS -t YOUR_ADDRESS --assetid 137493252 -a 1 --clawback L64GYN3IM763NDQJQD2IX35SCWQZRHWEMX55JTOUJ2PMHL6ZCMHLR4OJMU -o crown_transfer.txn 310 | ``` 311 | 312 | 6. Build the unsigned Group Transaction. 313 | 314 | ```bash 315 | $ cat crown_claim.txn crown_donation.txn crown_transfer.txn > claim.txn 316 | 317 | $ ./goal clerk group -i claim.txn -o claim.gtxn 318 | ``` 319 | 320 | 7. Split the Group Transaction and sign the single transactions (no longer valid if submitted as standalone). 321 | 322 | ```bash 323 | $ ./goal clerk split -i claim.gtxn -o unsigned_claim.txn 324 | 325 | $ ./goal clerk sign -i unsigned_claim-0.txn -o claim-0.stxn 326 | 327 | $ ./goal clerk sign -i unsigned_claim-1.txn -o claim-1.stxn 328 | 329 | $ ./goal clerk sign -i unsigned_claim-2.txn -p algorealm_law.teal -o claim-2.stxn 330 | ``` 331 | 332 | 8. Submit the signed Group Transaction: claim the Crown of Entropy and became the Randomic Majesty of Algorand! 333 | 334 | ```bash 335 | $ cat claim-0.stxn claim-1.stxn claim-2.stxn > claim.sgtxn 336 | 337 | $ ./goal clerk rawsend -f claim.sgtxn 338 | ``` 339 | 340 | ### 2. Claim the Sceptre of Proof 341 | 342 | 1. Save the [AlgoRealm Law](https://github.com/cusma/algorealm/blob/main/algorealm_law.teal) into your node directory. 343 | 2. Find out who owns the [Sceptre of Proof](https://algoexplorer.io/asset/137494385) (keep the `SCEPTRE_OWNER_ADDRESS`) and Opt-In. 344 | 345 | ```bash 346 | $ ./goal asset send -f YOUR_ADDRESS -t YOUR_ADDRESS --assetid 137494385 -a 0 347 | ``` 348 | 349 | 3. Write the unsigned `sceptre_claim.txn` Applicarion Call transaction passing `"str:YOUR_NAME"` as `--app-arg`. 350 | 351 | ```bash 352 | $ ./goal app call --app-id 137491307 -f YOUR_ADDRESS --app-arg "str:Sceptre" --app-arg "str:YOUR_NAME" -o sceptre_claim.txn 353 | ``` 354 | 355 | 4. Write the unsigned `sceptre_donation.txn` Payment transaction to the Rewards Pool specifying `YOUR_DONATION` in microALGOs. The claim will be successful if `YOUR_DONATION` is grater than the current one. 356 | 357 | ```bash 358 | $ ./goal clerk send -f YOUR_ADDRESS -t 737777777777777777777777777777777777777777777777777UFEJ2CI -a YOUR_DONATION -o sceptre_donation.txn 359 | ``` 360 | 361 | 5. Write the unsigned `sceptre_transfer.txn` Asset Transfer transaction form `SCEPTRE_OWNER_ADDRESS` to `YOUR_ADDRESS`. 362 | 363 | ```bash 364 | $ ./goal asset send -f SCEPTRE_OWNER_ADDRESS -t YOUR_ADDRESS --assetid 137494385 -a 1 --clawback L64GYN3IM763NDQJQD2IX35SCWQZRHWEMX55JTOUJ2PMHL6ZCMHLR4OJMU -o sceptre_transfer.txn 365 | ``` 366 | 367 | 6. Build the unsigned Group Transaction. 368 | 369 | ```bash 370 | $ cat sceptre_claim.txn sceptre_donation.txn sceptre_transfer.txn > claim.txn 371 | 372 | $ ./goal clerk group -i claim.txn -o claim.gtxn 373 | ``` 374 | 375 | 7. Split the Group Transaction and sign the single transactions (no longer valid if submitted as standalone). 376 | 377 | ```bash 378 | $ ./goal clerk split -i claim.gtxn -o unsigned_claim.txn 379 | 380 | $ ./goal clerk sign -i unsigned_claim-0.txn -o claim-0.stxn 381 | 382 | $ ./goal clerk sign -i unsigned_claim-1.txn -o claim-1.stxn 383 | 384 | $ ./goal clerk sign -i unsigned_claim-2.txn -p algorealm_law.teal -o claim-2.stxn 385 | ``` 386 | 387 | 8. Submit the signed Group Transaction: claim the Sceptre of Proof and became the Verifiable Majesty of Algorand! 388 | 389 | ```bash 390 | $ cat claim-0.stxn claim-1.stxn claim-2.stxn > claim.sgtxn 391 | 392 | $ ./goal clerk rawsend -f claim.sgtxn 393 | ``` 394 | 395 | ### 3. Claim the AlgoRealm Special Card 396 | 397 | You can also claim and trade the **AlgoRealm Special Card** using the goal CLI [following these instructions](https://github.com/cusma/algorealm/tree/main/card#readme). 398 | 399 |
400 | 401 | 402 | 403 | ## Tip the Dev 404 | 405 | If you enjoyed AlgoRealm or find it useful as free and open source learning example, consider tipping the Dev: 406 | 407 | `XODGWLOMKUPTGL3ZV53H3GZZWMCTJVQ5B2BZICFD3STSLA2LPSH6V6RW3I` 408 | 409 | Here you find the [AlgoRealm slide deck](https://docs.google.com/presentation/d/1pkE_VWuq_zPOtkc8tK8MYKPzdBwUQA8r5UgACpBpmvk/edit?usp=sharing) presented at Algorand's Office Hours! 410 | 411 | Join [AlgoRealm channel](https://t.me/algorealm)! 412 | 413 | ## ⭐️ Stargazers 414 | 415 | Special thanks to everyone who forked or starred the repository ❤️ 416 | 417 | [![Stargazers repo roster for @AlgoWorldNFT/algoworld-contracts](https://reporoster.com/stars/dark/AlgoRealm/algorealm)](https://github.com/AlgoRealm/algorealm/stargazers) 418 | 419 | [![Forkers repo roster for @AlgoWorldNFT/algoworld-contracts](https://reporoster.com/forks/dark/AlgoRealm/algorealm)](https://github.com/AlgoRealm/algorealm/network/members) 420 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "black" 3 | version = "22.12.0" 4 | description = "The uncompromising code formatter." 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=3.7" 8 | 9 | [package.dependencies] 10 | click = ">=8.0.0" 11 | mypy-extensions = ">=0.4.3" 12 | pathspec = ">=0.9.0" 13 | platformdirs = ">=2" 14 | tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} 15 | 16 | [package.extras] 17 | colorama = ["colorama (>=0.4.3)"] 18 | d = ["aiohttp (>=3.7.4)"] 19 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 20 | uvloop = ["uvloop (>=0.15.2)"] 21 | 22 | [[package]] 23 | name = "cffi" 24 | version = "1.15.1" 25 | description = "Foreign Function Interface for Python calling C code." 26 | category = "main" 27 | optional = false 28 | python-versions = "*" 29 | 30 | [package.dependencies] 31 | pycparser = "*" 32 | 33 | [[package]] 34 | name = "click" 35 | version = "8.1.3" 36 | description = "Composable command line interface toolkit" 37 | category = "dev" 38 | optional = false 39 | python-versions = ">=3.7" 40 | 41 | [package.dependencies] 42 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 43 | 44 | [[package]] 45 | name = "colorama" 46 | version = "0.4.6" 47 | description = "Cross-platform colored terminal text." 48 | category = "dev" 49 | optional = false 50 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 51 | 52 | [[package]] 53 | name = "docopt" 54 | version = "0.6.2" 55 | description = "Pythonic argument parser, that will make you smile" 56 | category = "main" 57 | optional = false 58 | python-versions = "*" 59 | 60 | [[package]] 61 | name = "msgpack" 62 | version = "1.0.4" 63 | description = "MessagePack serializer" 64 | category = "main" 65 | optional = false 66 | python-versions = "*" 67 | 68 | [[package]] 69 | name = "mypy-extensions" 70 | version = "0.4.3" 71 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 72 | category = "dev" 73 | optional = false 74 | python-versions = "*" 75 | 76 | [[package]] 77 | name = "pathspec" 78 | version = "0.10.3" 79 | description = "Utility library for gitignore style pattern matching of file paths." 80 | category = "dev" 81 | optional = false 82 | python-versions = ">=3.7" 83 | 84 | [[package]] 85 | name = "platformdirs" 86 | version = "2.6.2" 87 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 88 | category = "dev" 89 | optional = false 90 | python-versions = ">=3.7" 91 | 92 | [package.extras] 93 | docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.19.5)", "sphinx (>=5.3)"] 94 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest (>=7.2)"] 95 | 96 | [[package]] 97 | name = "prettytable" 98 | version = "3.5.0" 99 | description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" 100 | category = "main" 101 | optional = false 102 | python-versions = ">=3.7" 103 | 104 | [package.dependencies] 105 | wcwidth = "*" 106 | 107 | [package.extras] 108 | tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] 109 | 110 | [[package]] 111 | name = "py-algorand-sdk" 112 | version = "1.20.2" 113 | description = "Algorand SDK in Python" 114 | category = "main" 115 | optional = false 116 | python-versions = ">=3.8" 117 | 118 | [package.dependencies] 119 | msgpack = ">=1.0.0,<2" 120 | pycryptodomex = ">=3.6.0,<4" 121 | pynacl = ">=1.4.0,<2" 122 | 123 | [[package]] 124 | name = "pycparser" 125 | version = "2.21" 126 | description = "C parser in Python" 127 | category = "main" 128 | optional = false 129 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 130 | 131 | [[package]] 132 | name = "pycryptodomex" 133 | version = "3.16.0" 134 | description = "Cryptographic library for Python" 135 | category = "main" 136 | optional = false 137 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 138 | 139 | [[package]] 140 | name = "pynacl" 141 | version = "1.5.0" 142 | description = "Python binding to the Networking and Cryptography (NaCl) library" 143 | category = "main" 144 | optional = false 145 | python-versions = ">=3.6" 146 | 147 | [package.dependencies] 148 | cffi = ">=1.4.1" 149 | 150 | [package.extras] 151 | docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] 152 | tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"] 153 | 154 | [[package]] 155 | name = "tomli" 156 | version = "2.0.1" 157 | description = "A lil' TOML parser" 158 | category = "dev" 159 | optional = false 160 | python-versions = ">=3.7" 161 | 162 | [[package]] 163 | name = "wcwidth" 164 | version = "0.2.5" 165 | description = "Measures the displayed width of unicode strings in a terminal" 166 | category = "main" 167 | optional = false 168 | python-versions = "*" 169 | 170 | [metadata] 171 | lock-version = "1.1" 172 | python-versions = "^3.10" 173 | content-hash = "fe2b504c5c1fda1a1b8ae6b9eb23fbe56a89d2f4b317c984338f9f07789b7694" 174 | 175 | [metadata.files] 176 | black = [] 177 | cffi = [ 178 | {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, 179 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, 180 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, 181 | {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, 182 | {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, 183 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, 184 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, 185 | {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, 186 | {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, 187 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, 188 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, 189 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, 190 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, 191 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, 192 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, 193 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, 194 | {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, 195 | {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, 196 | {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, 197 | {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, 198 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, 199 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, 200 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, 201 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, 202 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, 203 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, 204 | {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, 205 | {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, 206 | {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, 207 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, 208 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, 209 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, 210 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, 211 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, 212 | {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, 213 | {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, 214 | {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, 215 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, 216 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, 217 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, 218 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, 219 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, 220 | {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, 221 | {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, 222 | {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, 223 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, 224 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, 225 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, 226 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, 227 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, 228 | {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, 229 | {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, 230 | {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, 231 | {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, 232 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, 233 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, 234 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, 235 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, 236 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, 237 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, 238 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, 239 | {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, 240 | {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, 241 | {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, 242 | ] 243 | click = [ 244 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 245 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 246 | ] 247 | colorama = [] 248 | docopt = [ 249 | {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, 250 | ] 251 | msgpack = [ 252 | {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, 253 | {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"}, 254 | {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"}, 255 | {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6"}, 256 | {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa"}, 257 | {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6"}, 258 | {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba"}, 259 | {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e"}, 260 | {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db"}, 261 | {file = "msgpack-1.0.4-cp310-cp310-win32.whl", hash = "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef"}, 262 | {file = "msgpack-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075"}, 263 | {file = "msgpack-1.0.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52"}, 264 | {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9"}, 265 | {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9"}, 266 | {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08"}, 267 | {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8"}, 268 | {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6"}, 269 | {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae"}, 270 | {file = "msgpack-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6"}, 271 | {file = "msgpack-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661"}, 272 | {file = "msgpack-1.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c"}, 273 | {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0"}, 274 | {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227"}, 275 | {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff"}, 276 | {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd"}, 277 | {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e"}, 278 | {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236"}, 279 | {file = "msgpack-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44"}, 280 | {file = "msgpack-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1"}, 281 | {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d"}, 282 | {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab"}, 283 | {file = "msgpack-1.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb"}, 284 | {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9"}, 285 | {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e"}, 286 | {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1"}, 287 | {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e"}, 288 | {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43"}, 289 | {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243"}, 290 | {file = "msgpack-1.0.4-cp38-cp38-win32.whl", hash = "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2"}, 291 | {file = "msgpack-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6"}, 292 | {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae"}, 293 | {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55"}, 294 | {file = "msgpack-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da"}, 295 | {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f"}, 296 | {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92"}, 297 | {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f"}, 298 | {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624"}, 299 | {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8"}, 300 | {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae"}, 301 | {file = "msgpack-1.0.4-cp39-cp39-win32.whl", hash = "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c"}, 302 | {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"}, 303 | {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, 304 | ] 305 | mypy-extensions = [ 306 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 307 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 308 | ] 309 | pathspec = [] 310 | platformdirs = [] 311 | prettytable = [] 312 | py-algorand-sdk = [] 313 | pycparser = [ 314 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 315 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 316 | ] 317 | pycryptodomex = [] 318 | pynacl = [ 319 | {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, 320 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, 321 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, 322 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, 323 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, 324 | {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, 325 | {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, 326 | {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, 327 | {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, 328 | {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, 329 | ] 330 | tomli = [ 331 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 332 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 333 | ] 334 | wcwidth = [] 335 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "algorealm-cli" 3 | version = "0.4.0" 4 | description = "An interactive game of randomness powered by Algorand blockchain (by cusma)" 5 | authors = ["cosimo.bassi@gmail.com"] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10" 10 | docopt = "^0.6.2" 11 | msgpack = "^1.0.4" 12 | py-algorand-sdk = "^1.20.2" 13 | prettytable = "^3.5.0" 14 | 15 | [tool.poetry.dev-dependencies] 16 | black = "^22.12.0" 17 | 18 | [build-system] 19 | requires = ["poetry-core>=1.0.0"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "semanticCommits": true, 4 | "ignoreDeps": [], 5 | "schedule": "before 3am on the first day of the month", 6 | "assignees": ["cusma"], 7 | "baseBranches": ["develop"], 8 | "separateMajorMinor": true, 9 | "rebaseStalePrs": true, 10 | "lockFileMaintenance": { 11 | "enabled": true, 12 | "extends": "schedule:monthly" 13 | }, 14 | "packageRules": [ 15 | { 16 | "matchPackagePatterns": ["*"], 17 | "matchUpdateTypes": ["minor", "patch"], 18 | "groupName": "all non-major dependencies", 19 | "groupSlug": "all-minor-patch" 20 | } 21 | ], 22 | "docker": { 23 | "enabled": true 24 | }, 25 | "python": { 26 | "enabled": false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlgoRealm/algorealm/a8a5547156612534f8c0edd8da9270338edc63a8/src/__init__.py -------------------------------------------------------------------------------- /src/actions.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import math 3 | import sys 4 | import traceback 5 | from getpass import getpass 6 | 7 | import msgpack 8 | from algosdk.account import address_from_private_key 9 | from algosdk.atomic_transaction_composer import ( 10 | AccountTransactionSigner, 11 | AtomicTransactionComposer, 12 | AtomicTransactionResponse, 13 | LogicSigTransactionSigner, 14 | TransactionWithSigner, 15 | ) 16 | from algosdk.constants import MNEMONIC_LEN 17 | from algosdk.future.transaction import ( 18 | ApplicationNoOpTxn, 19 | AssetOptInTxn, 20 | AssetTransferTxn, 21 | LogicSigAccount, 22 | LogicSigTransaction, 23 | PaymentTxn, 24 | Transaction, 25 | wait_for_confirmation, 26 | write_to_file, 27 | ) 28 | from algosdk.mnemonic import to_private_key 29 | from algosdk.util import microalgos_to_algos 30 | from algosdk.v2client.algod import AlgodClient 31 | 32 | from const import ( 33 | ASA_STATE_OBSERVER_APP_ID, 34 | CARD_ID, 35 | CROWN_ID, 36 | REWARDS_POOL, 37 | ROYALTY_PERC, 38 | SCEPTRE_ID, 39 | ) 40 | 41 | 42 | def get_user() -> AccountTransactionSigner: 43 | """ 44 | Returns: 45 | Algorand User Account 46 | """ 47 | mnemonic_phrase = getpass(prompt="Mnemonic (word_1 word_2 ... word_25):") 48 | try: 49 | assert len(mnemonic_phrase.split()) == MNEMONIC_LEN 50 | except AssertionError: 51 | quit('\n⚠️ Enter mnemonic phrase, formatted as: "word_1 ... word_25"') 52 | return AccountTransactionSigner(to_private_key(mnemonic_phrase)) 53 | 54 | 55 | def get_contract_account(program) -> LogicSigTransactionSigner: 56 | """ 57 | Args: 58 | program: TEAL bytecode 59 | 60 | Returns: 61 | Algorand Contract Account 62 | """ 63 | return LogicSigTransactionSigner( 64 | lsig=LogicSigAccount(base64.decodebytes(program.encode())) 65 | ) 66 | 67 | 68 | def opt_in_algorealm_nft( 69 | client: AlgodClient, 70 | user: AccountTransactionSigner, 71 | nft_id: int, 72 | ) -> AtomicTransactionResponse: 73 | """ 74 | Opt-In AlgoRealm NFT 75 | 76 | Args: 77 | client: Algod Client 78 | user: Algorand User Account 79 | nft_id: AlgoRealm NFT ID (Crown, Sceptre, Card) 80 | 81 | Returns: 82 | Execute NFT Opt-In transaction 83 | """ 84 | 85 | user_address = address_from_private_key(user.private_key) 86 | 87 | atc = AtomicTransactionComposer() 88 | params = client.suggested_params() 89 | 90 | opt_in_nft_txn = AssetOptInTxn( 91 | sender=user_address, 92 | sp=params, 93 | index=nft_id, 94 | ) 95 | atc.add_transaction( 96 | TransactionWithSigner( 97 | txn=opt_in_nft_txn, 98 | signer=user, 99 | ) 100 | ) 101 | return atc.execute(client=client, wait_rounds=4) 102 | 103 | 104 | def claim_algorealm_nft( 105 | client: AlgodClient, 106 | algorealm_app_id: int, 107 | algorealm_law: LogicSigTransactionSigner, 108 | user: AccountTransactionSigner, 109 | current_owner: str, 110 | claim_select: str, 111 | majesty_name: str, 112 | donation_amount: int, 113 | nft_id: int, 114 | ) -> AtomicTransactionResponse: 115 | """ 116 | Claim AlgoRealm Majesty Title and NFT 117 | 118 | Args: 119 | client: Algod Client 120 | algorealm_app_id: AlgoRealm Application ID 121 | algorealm_law: AlgoRealm Contract Account 122 | user: Algorand User Account 123 | current_owner: Address of current NFT owner (Crown, Sceptre) 124 | claim_select: Title selector (Crown, Sceptre) 125 | majesty_name: New Majesty nickname 126 | donation_amount: New Majesty donation to the Rewards Pool 127 | nft_id: AlgoRealm NFT ID (Crown, Sceptre) 128 | 129 | Returns: 130 | 131 | """ 132 | assert claim_select == "Crown" or claim_select == "Sceptre" 133 | 134 | user_address = address_from_private_key(user.private_key) 135 | 136 | atc = AtomicTransactionComposer() 137 | params = client.suggested_params() 138 | 139 | claim_nft_txn = ApplicationNoOpTxn( 140 | sender=user_address, 141 | sp=params, 142 | index=algorealm_app_id, 143 | app_args=[claim_select.encode(), majesty_name.encode()], 144 | ) 145 | atc.add_transaction( 146 | TransactionWithSigner( 147 | txn=claim_nft_txn, 148 | signer=user, 149 | ) 150 | ) 151 | 152 | donation_txn = PaymentTxn( 153 | sender=user_address, 154 | sp=params, 155 | receiver=REWARDS_POOL, 156 | amt=donation_amount, 157 | ) 158 | atc.add_transaction( 159 | TransactionWithSigner( 160 | txn=donation_txn, 161 | signer=user, 162 | ) 163 | ) 164 | 165 | nft_transfer = AssetTransferTxn( 166 | sender=algorealm_law.lsig.address(), 167 | sp=params, 168 | receiver=user_address, 169 | amt=1, 170 | index=nft_id, 171 | revocation_target=current_owner, 172 | ) 173 | atc.add_transaction( 174 | TransactionWithSigner( 175 | txn=nft_transfer, 176 | signer=algorealm_law, 177 | ) 178 | ) 179 | return atc.execute(client=client, wait_rounds=4) 180 | 181 | 182 | def proof_asa_amount_eq_txn( 183 | client: AlgodClient, 184 | owner_address: str, 185 | asa_id: int, 186 | asa_amount: int, 187 | ) -> ApplicationNoOpTxn: 188 | 189 | params = client.suggested_params() 190 | 191 | method = "AsaAmountEq" 192 | 193 | return ApplicationNoOpTxn( 194 | sender=owner_address, 195 | sp=params, 196 | index=ASA_STATE_OBSERVER_APP_ID, 197 | app_args=[method.encode(), asa_amount], 198 | foreign_assets=[asa_id], 199 | accounts=[owner_address], 200 | ) 201 | 202 | 203 | def claim_card( 204 | client: AlgodClient, 205 | card_contract: LogicSigTransactionSigner, 206 | user: AccountTransactionSigner, 207 | ) -> AtomicTransactionResponse: 208 | 209 | user_address = address_from_private_key(user.private_key) 210 | 211 | atc = AtomicTransactionComposer() 212 | params = client.suggested_params() 213 | 214 | proof_crown_ownership = proof_asa_amount_eq_txn( 215 | client=client, 216 | owner_address=user_address, 217 | asa_id=CROWN_ID, 218 | asa_amount=1, 219 | ) 220 | atc.add_transaction(TransactionWithSigner(txn=proof_crown_ownership, signer=user)) 221 | 222 | proof_sceptre_ownership = proof_asa_amount_eq_txn( 223 | client=client, 224 | owner_address=user_address, 225 | asa_id=SCEPTRE_ID, 226 | asa_amount=1, 227 | ) 228 | atc.add_transaction(TransactionWithSigner(txn=proof_sceptre_ownership, signer=user)) 229 | 230 | nft_card_xfer = AssetTransferTxn( 231 | sender=card_contract.lsig.address(), 232 | sp=params, 233 | receiver=user_address, 234 | amt=1, 235 | index=CARD_ID, 236 | revocation_target=card_contract.lsig.address(), 237 | ) 238 | atc.add_transaction( 239 | TransactionWithSigner( 240 | txn=nft_card_xfer, 241 | signer=card_contract, 242 | ) 243 | ) 244 | return atc.execute(client=client, wait_rounds=4) 245 | 246 | 247 | def card_order( 248 | client: AlgodClient, 249 | card_contract: LogicSigTransactionSigner, 250 | buyer: AccountTransactionSigner, 251 | seller_address: str, 252 | royalty_collector_1_addr: str, 253 | royalty_collector_2_addr: str, 254 | price: int, 255 | ) -> list[Transaction]: 256 | 257 | buyer_address = address_from_private_key(buyer.private_key) 258 | 259 | proof_crown_ownership = proof_asa_amount_eq_txn( 260 | client=client, 261 | owner_address=seller_address, 262 | asa_id=CROWN_ID, 263 | asa_amount=1, 264 | ) 265 | 266 | proof_sceptre_ownership = proof_asa_amount_eq_txn( 267 | client=client, 268 | owner_address=seller_address, 269 | asa_id=SCEPTRE_ID, 270 | asa_amount=1, 271 | ) 272 | 273 | params = client.suggested_params() 274 | 275 | nft_card_payment = PaymentTxn( 276 | sender=buyer_address, 277 | sp=params, 278 | receiver=seller_address, 279 | amt=price, 280 | ) 281 | 282 | royalty_amount = math.ceil(price * ROYALTY_PERC / 100) 283 | 284 | royalty_1_payment = PaymentTxn( 285 | sender=seller_address, 286 | sp=params, 287 | receiver=royalty_collector_1_addr, 288 | amt=royalty_amount, 289 | ) 290 | 291 | royalty_2_payment = PaymentTxn( 292 | sender=seller_address, 293 | sp=params, 294 | receiver=royalty_collector_2_addr, 295 | amt=royalty_amount, 296 | ) 297 | 298 | nft_card_xfer = AssetTransferTxn( 299 | sender=card_contract.lsig.address(), 300 | sp=params, 301 | receiver=buyer_address, 302 | amt=1, 303 | index=CARD_ID, 304 | revocation_target=seller_address, 305 | ) 306 | 307 | trade_gtxn = [ 308 | proof_crown_ownership, 309 | proof_sceptre_ownership, 310 | nft_card_payment, 311 | royalty_1_payment, 312 | royalty_2_payment, 313 | nft_card_xfer, 314 | ] 315 | 316 | sig_nft_card_payment = trade_gtxn[2].sign(buyer.private_key) 317 | sig_nft_card_xfer = LogicSigTransaction(trade_gtxn[5], card_contract.lsig) 318 | trade_gtxn[2] = sig_nft_card_payment 319 | trade_gtxn[5] = sig_nft_card_xfer 320 | write_to_file(trade_gtxn, "trade.gtxn", overwrite=True) 321 | 322 | return trade_gtxn 323 | 324 | 325 | def notify( 326 | client: AlgodClient, 327 | user: AccountTransactionSigner, 328 | seller_address: str, 329 | trade_gtxn: list[Transaction], 330 | ) -> AtomicTransactionResponse: 331 | user_address = address_from_private_key(user.private_key) 332 | 333 | atc = AtomicTransactionComposer() 334 | params = client.suggested_params() 335 | 336 | note = { 337 | "buy_order": "AlgoRealm Special Card", 338 | "asset_id": CARD_ID, 339 | "algo_amount": microalgos_to_algos(trade_gtxn[2].amt), 340 | "algo_royalty": microalgos_to_algos(trade_gtxn[3].amt + trade_gtxn[4].amt), 341 | "last_valid_block": trade_gtxn[2].last_valid_round, 342 | } 343 | 344 | bytes_note = msgpack.packb(note) 345 | 346 | notification_txn = PaymentTxn( 347 | sender=user_address, 348 | sp=params, 349 | receiver=seller_address, 350 | amt=0, 351 | note=bytes_note, 352 | ) 353 | atc.add_transaction( 354 | TransactionWithSigner( 355 | txn=notification_txn, 356 | signer=user, 357 | ) 358 | ) 359 | 360 | return atc.execute(client=client, wait_rounds=4) 361 | 362 | 363 | def sell_card( 364 | client: AlgodClient, 365 | user: AccountTransactionSigner, 366 | trade_gtxn: list, 367 | ) -> dict: 368 | 369 | signed_crown_proof = trade_gtxn[0].sign(user.private_key) 370 | signed_sceptre_proof = trade_gtxn[1].sign(user.private_key) 371 | signed_royalty_1 = trade_gtxn[3].sign(user.private_key) 372 | signed_royalty_2 = trade_gtxn[4].sign(user.private_key) 373 | 374 | trade_gtxn[0] = signed_crown_proof 375 | trade_gtxn[1] = signed_sceptre_proof 376 | trade_gtxn[3] = signed_royalty_1 377 | trade_gtxn[4] = signed_royalty_2 378 | 379 | gtxn_id = client.send_transactions(trade_gtxn) 380 | return wait_for_confirmation(client, gtxn_id, wait_rounds=4) 381 | 382 | 383 | def verify_buy_order( 384 | card_contract: LogicSigTransactionSigner, 385 | seller_address: str, 386 | royalty_collector_1_addr: str, 387 | royalty_collector_2_addr: str, 388 | trade_gtxn: list, 389 | ) -> list: 390 | # Check TXN 0: Crown Proof of Ownership 391 | try: 392 | assert trade_gtxn[0].type == "appl" 393 | assert trade_gtxn[0].index == ASA_STATE_OBSERVER_APP_ID 394 | assert trade_gtxn[0].app_args[0] == b"AsaAmountEq" 395 | assert trade_gtxn[0].app_args[1] == b"\x00\x00\x00\x00\x00\x00\x00\x01" 396 | assert trade_gtxn[0].foreign_assets[0] == CROWN_ID 397 | assert trade_gtxn[0].accounts[0] == seller_address 398 | assert trade_gtxn[0].sender == seller_address 399 | assert trade_gtxn[0].fee <= 1000 400 | assert trade_gtxn[0].rekey_to is None 401 | except AssertionError: 402 | _, _, tb = sys.exc_info() 403 | tb_info = traceback.extract_tb(tb) 404 | filename, line, func, text = tb_info[-1] 405 | quit("Transaction 0 - Crown Proof of Ownership is invalid: {}".format(text)) 406 | 407 | # Check TXN 1: Sceptre Proof of Ownership 408 | try: 409 | assert trade_gtxn[1].type == "appl" 410 | assert trade_gtxn[1].index == ASA_STATE_OBSERVER_APP_ID 411 | assert trade_gtxn[1].app_args[0] == b"AsaAmountEq" 412 | assert trade_gtxn[1].app_args[1] == b"\x00\x00\x00\x00\x00\x00\x00\x01" 413 | assert trade_gtxn[1].foreign_assets[0] == SCEPTRE_ID 414 | assert trade_gtxn[1].accounts[0] == seller_address 415 | assert trade_gtxn[1].sender == seller_address 416 | assert trade_gtxn[1].fee <= 1000 417 | assert trade_gtxn[1].rekey_to is None 418 | except AssertionError: 419 | _, _, tb = sys.exc_info() 420 | tb_info = traceback.extract_tb(tb) 421 | filename, line, func, text = tb_info[-1] 422 | quit("Transaction 1 - Sceptre Proof of Ownership is invalid: {}".format(text)) 423 | 424 | # Check TXN 2: Card Payment 425 | try: 426 | assert trade_gtxn[2].transaction.type == "pay" 427 | assert trade_gtxn[2].transaction.receiver == seller_address 428 | except AssertionError: 429 | _, _, tb = sys.exc_info() 430 | tb_info = traceback.extract_tb(tb) 431 | filename, line, func, text = tb_info[-1] 432 | quit("Transaction 2 - Card Payment is invalid: {}".format(text)) 433 | 434 | # Check TXN 3: Royalty 1 Payment 435 | try: 436 | assert trade_gtxn[3].type == "pay" 437 | assert trade_gtxn[3].sender == seller_address 438 | assert trade_gtxn[3].receiver == royalty_collector_1_addr 439 | assert trade_gtxn[3].fee <= 1000 440 | assert trade_gtxn[3].rekey_to is None 441 | except AssertionError: 442 | _, _, tb = sys.exc_info() 443 | tb_info = traceback.extract_tb(tb) 444 | filename, line, func, text = tb_info[-1] 445 | quit("Transaction 3 - Royalty 1 Payment is invalid: {}".format(text)) 446 | 447 | # Check TXN 4: Royalty 3 Payment 448 | try: 449 | assert trade_gtxn[4].type == "pay" 450 | assert trade_gtxn[4].sender == seller_address 451 | assert trade_gtxn[4].receiver == royalty_collector_2_addr 452 | assert trade_gtxn[4].fee <= 1000 453 | assert trade_gtxn[4].rekey_to is None 454 | except AssertionError: 455 | _, _, tb = sys.exc_info() 456 | tb_info = traceback.extract_tb(tb) 457 | filename, line, func, text = tb_info[-1] 458 | quit("Transaction 4 - Royalty 2 Payment is invalid: {}".format(text)) 459 | 460 | # Check TXN 5: Card Transfer 461 | try: 462 | assert trade_gtxn[5].transaction.type == "axfer" 463 | assert trade_gtxn[5].transaction.index == CARD_ID 464 | assert trade_gtxn[5].transaction.amount == 1 465 | assert trade_gtxn[5].transaction.sender == card_contract.lsig.address() 466 | assert trade_gtxn[5].transaction.receiver == trade_gtxn[2].transaction.sender 467 | assert trade_gtxn[5].transaction.revocation_target == seller_address 468 | assert trade_gtxn[5].transaction.fee <= 1000 469 | assert trade_gtxn[5].transaction.rekey_to is None 470 | except AssertionError: 471 | _, _, tb = sys.exc_info() 472 | tb_info = traceback.extract_tb(tb) 473 | filename, line, func, text = tb_info[-1] 474 | quit("Transaction 5 - Card Transfer is invalid: {}".format(text)) 475 | 476 | return trade_gtxn 477 | 478 | 479 | def order_summary(client: AlgodClient, trade_gtxn: list) -> str: 480 | current_round = client.status()["last-round"] 481 | last_valid_round = trade_gtxn[2].transaction.last_valid_round 482 | remaning_rounds = last_valid_round - current_round 483 | if remaning_rounds <= 0: 484 | remaning_rounds = "Buy order expired!" 485 | 486 | return f""" 487 | * =========================== ORDER SUMMARY =========================== * 488 | 489 | BUYER:\t{trade_gtxn[2].transaction.sender} 490 | SELLER:\t{trade_gtxn[2].transaction.receiver} 491 | AMOUNT:\t{trade_gtxn[2].transaction.amt / 10 ** 6} ALGO 492 | ROYALTY:\t{(trade_gtxn[3].amt + trade_gtxn[4].amt) / 10 ** 6} ALGO 493 | 494 | BUY-ORDER VALIDITY REMAINING BLOCKS: {remaning_rounds} 495 | 496 | * ===================================================================== * 497 | """ 498 | -------------------------------------------------------------------------------- /src/algorealm.py: -------------------------------------------------------------------------------- 1 | """ 2 | AlgoRealm, only generous heart will ever rule over Algorand. (by cusma) 3 | 4 | Usage: 5 | algorealm.py poem 6 | algorealm.py dynasty [--test] 7 | algorealm.py longevity (--crown | --sceptre) [--test] 8 | algorealm.py braveness (--crown | --sceptre) [--test] 9 | algorealm.py claim-majesty (--crown | --sceptre) [--test] 10 | algorealm.py claim-card 11 | algorealm.py buy-order [--notify] 12 | algorealm.py verify-order 13 | algorealm.py sell-card 14 | algorealm.py [--help] 15 | 16 | Commands: 17 | poem AlgoRealm's poem. 18 | dynasty Print the glorious dynasty of AlgoRealm's Majesties. 19 | longevity Print AlgoRealm's Majesties longevity. 20 | braveness Print AlgoRealm's Majesties braveness. 21 | claim-majesty Claim the Crown of Entropy or the Sceptre of Proof, become Majesty of Algorand. 22 | claim-card Brake the spell and claim the AlgoRealm Card by AlgoWorld. 23 | buy-order Place an order for the AlgoRealm Card. 24 | verify-order Verify the partially signed AlgoRealm Card buy order. 25 | sell-card Sell the AlgoRealm Card (paying a 10% royalty). 26 | 27 | Options: 28 | -n, --notify Notify the Seller about your buy order on-chain. 29 | -t, --test TestNet mode 30 | -h, --help 31 | """ 32 | 33 | 34 | import sys 35 | 36 | from algosdk import util 37 | from algosdk.error import AlgodHTTPError 38 | from algosdk.future.transaction import retrieve_from_file 39 | from algosdk.v2client.algod import AlgodClient 40 | from algosdk.v2client.indexer import IndexerClient 41 | from docopt import docopt 42 | from prettytable import PrettyTable 43 | 44 | import actions 45 | import query 46 | from const import ( 47 | ALGOD_ADDRESS, 48 | ALGOREALM_APP_ID, 49 | ALGOREALM_CARD_FIRST_BLOCK, 50 | ALGOREALM_FIRST_BLOCK, 51 | ALGOREALM_LAW_BYTECODE, 52 | CARD_CONTRACT_BYTECODE, 53 | CARD_ID, 54 | CROWN_ID, 55 | HEADER, 56 | INDEXER_ADDRESS, 57 | ROYALTY_COLLECTOR_1, 58 | ROYALTY_COLLECTOR_2, 59 | SCEPTRE_ID, 60 | TEST_ALGOD_ADDRESS, 61 | TEST_ALGOREALM_APP_ID, 62 | TEST_ALGOREALM_FIRST_BLOCK, 63 | TEST_ALGOREALM_LAW_BYTECODE, 64 | TEST_CROWN_ID, 65 | TEST_INDEXER_ADDRESS, 66 | TEST_SCEPTRE_ID, 67 | ) 68 | 69 | 70 | def build_algod_client( 71 | api_address: str = ALGOD_ADDRESS, 72 | test: bool = False, 73 | ) -> AlgodClient: 74 | if test: 75 | api_address = TEST_ALGOD_ADDRESS 76 | return AlgodClient(algod_token="", algod_address=api_address, headers=HEADER) 77 | 78 | 79 | def build_indexer_client( 80 | api_address: str = INDEXER_ADDRESS, 81 | test: bool = False, 82 | ) -> IndexerClient: 83 | if test: 84 | api_address = TEST_INDEXER_ADDRESS 85 | return IndexerClient(indexer_token="", indexer_address=api_address, headers=HEADER) 86 | 87 | 88 | def title(): 89 | return r""" 90 | __ __ ___ __ __ 91 | \*) \*) \*/ (*/ (*/ 92 | \*\_\*\_|O|_/*/_/*/ 93 | \_______________/ 94 | _ __ _______ __ 95 | / \ [ | |_ __ \ [ | 96 | / _ \ | | .--./) .--. | |__) | .---. ,--. | | _ .--..--. 97 | / ___ \ | | / /'`\;/ .'`\ \ | __ / / /__\\`'_\ : | | [ `.-. .-. | 98 | _/ / \ \_ | | \ \._//| \__. |_| | \ \_| \__.,// | |, | | | | | | | | 99 | |____| |____|[___].',__` '.__.'|____| |___|'.__.'\'-;__/[___][___||__||__] 100 | ( ( __)) 101 | by cusma 102 | """ 103 | 104 | 105 | def poem(): 106 | return r""" 107 | ,-----------------------------------------. 108 | (_\ \ 109 | | There was a time | 110 | | When nothing but Entropy was there. | 111 | | Then came the cryptographic Proof, | 112 | | And took it care. | 113 | | | 114 | | Verifiability of Randomness, | 115 | | Since genesis block, | 116 | | Brings Consensus over realm vastness, | 117 | | So Algorand shall not fork. | 118 | _| | 119 | (_/___________________(*)___________________/ 120 | \\ 121 | )) 122 | ^ 123 | """ 124 | 125 | 126 | def main(): 127 | if len(sys.argv) == 1: 128 | # Display help if no arguments, see: 129 | # https://github.com/docopt/docopt/issues/420#issuecomment-405018014 130 | sys.argv.append("--help") 131 | 132 | args = docopt(__doc__) 133 | 134 | print(title()) 135 | 136 | if args["poem"]: 137 | return print(poem()) 138 | 139 | # API 140 | algod_client = build_algod_client(test=args["--test"]) 141 | indexer_client = build_indexer_client(test=args["--test"]) 142 | 143 | if args["--test"]: 144 | crown_nft_id = TEST_CROWN_ID 145 | sceptre_nft_id = TEST_SCEPTRE_ID 146 | algorealm_app_id = TEST_ALGOREALM_APP_ID 147 | algorealm_contract = TEST_ALGOREALM_LAW_BYTECODE 148 | algorealm_first_round = TEST_ALGOREALM_FIRST_BLOCK 149 | else: 150 | crown_nft_id = CROWN_ID 151 | sceptre_nft_id = SCEPTRE_ID 152 | algorealm_app_id = ALGOREALM_APP_ID 153 | algorealm_contract = ALGOREALM_LAW_BYTECODE 154 | algorealm_first_round = ALGOREALM_FIRST_BLOCK 155 | 156 | # CLI 157 | if args["dynasty"]: 158 | claims = query.claims_history( 159 | client=indexer_client, 160 | algorealm_app_id=algorealm_app_id, 161 | algorealm_first_round=algorealm_first_round, 162 | ) 163 | 164 | print("\t\t\t\t*** DYNASTY ***\n") 165 | return print(*["\n", *query.dynasty(claims)]) 166 | 167 | if args["longevity"]: 168 | claims = query.claims_history( 169 | client=indexer_client, 170 | algorealm_app_id=algorealm_app_id, 171 | algorealm_first_round=algorealm_first_round, 172 | ) 173 | latest_block = algod_client.status()["last-round"] 174 | 175 | if args["--crown"]: 176 | majesty_title = "👑 RANDOMIC" 177 | claim_select = "Crown" 178 | else: 179 | majesty_title = "🪄 VERIFIABLE" 180 | claim_select = "Sceptre" 181 | 182 | majesty_longevity = query.longevity(claims, latest_block, claim_select) 183 | 184 | longevity_table = PrettyTable() 185 | longevity_table.field_names = ["Majesty Name", "Longevity (blocks)"] 186 | longevity_table.add_rows( 187 | [[claim["name"], claim["longevity"]] for claim in majesty_longevity] 188 | ) 189 | 190 | print(f"\t\t*** {majesty_title} MAJESTY LONGEVITY ***\n") 191 | return print(longevity_table) 192 | 193 | if args["braveness"]: 194 | claims = query.claims_history( 195 | client=indexer_client, 196 | algorealm_app_id=algorealm_app_id, 197 | algorealm_first_round=algorealm_first_round, 198 | ) 199 | 200 | if args["--crown"]: 201 | majesty_title = "👑 RANDOMIC" 202 | claim_select = "Crown" 203 | else: 204 | majesty_title = "🪄 VERIFIABLE" 205 | claim_select = "Sceptre" 206 | 207 | majesty_braveness = query.braveness(claims, claim_select) 208 | 209 | braveness_table = PrettyTable() 210 | braveness_table.field_names = ["Majesty Name", "Braveness"] 211 | braveness_table.add_rows( 212 | [[claim["name"], claim["braveness"]] for claim in majesty_braveness] 213 | ) 214 | 215 | print(f"\t\t*** {majesty_title} MAJESTY BRAVENESS ***\n") 216 | return print(braveness_table) 217 | 218 | if args["claim-majesty"]: 219 | majesty_name = args[""] 220 | 221 | if args["--crown"]: 222 | proclaim = ( 223 | f"\n👑 Glory to {majesty_name}, the Randomic Majesty of Algorand! 🎉\n" 224 | ) 225 | claim_select = "Crown" 226 | nft_id = crown_nft_id 227 | else: 228 | proclaim = ( 229 | f"\n🪄 Glory to {majesty_name}, the Verifiable Majesty of Algorand! 🎉\n" 230 | ) 231 | claim_select = "Sceptre" 232 | nft_id = sceptre_nft_id 233 | 234 | user = actions.get_user() 235 | algorealm_law = actions.get_contract_account(algorealm_contract) 236 | current_owner = query.current_owner( 237 | indexer_client, nft_id, algorealm_first_round 238 | ) 239 | donation = int(args[""]) 240 | nft_name = algod_client.asset_info(nft_id)["params"]["name"] 241 | 242 | opted_in = False 243 | while not opted_in: 244 | optin_choice = input( 245 | f"Do you want to opt-in the {nft_name} (ID: {nft_id})? (Y/n)" 246 | ) 247 | if optin_choice.lower() == "y": 248 | actions.opt_in_algorealm_nft(algod_client, user, nft_id) 249 | opted_in = True 250 | elif optin_choice.lower() == "n": 251 | opted_in = True 252 | 253 | print(f"Claiming the {nft_name} as donating {donation / 10 ** 6} ALGO...\n") 254 | try: 255 | actions.claim_algorealm_nft( 256 | client=algod_client, 257 | algorealm_app_id=algorealm_app_id, 258 | algorealm_law=algorealm_law, 259 | user=user, 260 | current_owner=current_owner, 261 | claim_select=claim_select, 262 | majesty_name=majesty_name, 263 | donation_amount=donation, 264 | nft_id=nft_id, 265 | ) 266 | return print(proclaim) 267 | except AlgodHTTPError: 268 | quit( 269 | "\n☹️ Were you too stingy? Only generous hearts will rule " 270 | "over Algorand Realm!\n️" 271 | ) 272 | 273 | elif args["claim-card"]: 274 | if algod_client.status()["last-round"] <= ALGOREALM_CARD_FIRST_BLOCK: 275 | return print( 276 | "🔐 The spell can be broken starting from the block " 277 | f"{ALGOREALM_CARD_FIRST_BLOCK}... ⏳\n" 278 | ) 279 | 280 | user = actions.get_user() 281 | card_contract = actions.get_contract_account(CARD_CONTRACT_BYTECODE) 282 | card_contract_info = algod_client.account_info(card_contract.lsig.address()) 283 | nft_name = algod_client.asset_info(CARD_ID)["params"]["name"] 284 | 285 | assets = card_contract_info["assets"] 286 | 287 | card_nft = list(filter(lambda asset: asset["asset-id"] == CARD_ID, assets))[0] 288 | 289 | if card_nft["amount"] == 0: 290 | return print( 291 | "🔓 The enchanted coffer is empty! " 292 | "The AlgoRealm Special Card has been claimed!\n" 293 | ) 294 | 295 | opted_in = False 296 | while not opted_in: 297 | optin_choice = input( 298 | f"Do you want to opt-in the {nft_name} (ID: {CARD_ID})? (Y/n)" 299 | ) 300 | if optin_choice.lower() == "y": 301 | actions.opt_in_algorealm_nft(algod_client, user, CARD_ID) 302 | opted_in = True 303 | elif optin_choice.lower() == "n": 304 | opted_in = True 305 | 306 | print("\n✨ Whispering words of wisdom...") 307 | try: 308 | actions.claim_card(algod_client, card_contract, user) 309 | return print( 310 | f"\n � The spell has been broken! " 311 | f"The AlgoRealm Special Card is yours! 🎉\n" 312 | ) 313 | except AlgodHTTPError: 314 | quit( 315 | "\nOnly the generous heart of the Great Majesty of Algorand " 316 | "can break the spell!\n" 317 | "Conquer both the 👑 Crown of Entropy and the 🪄 Sceptre " 318 | "of Proof first!\n" 319 | ) 320 | 321 | if args["buy-order"]: 322 | user = actions.get_user() 323 | card_contract = actions.get_contract_account(CARD_CONTRACT_BYTECODE) 324 | nft_name = algod_client.asset_info(CARD_ID)["params"]["name"] 325 | 326 | opted_in = False 327 | while not opted_in: 328 | optin_choice = input( 329 | f"Do you want to opt-in the {nft_name} (ID: {CARD_ID})? (Y/n)" 330 | ) 331 | if optin_choice.lower() == "y": 332 | actions.opt_in_algorealm_nft(algod_client, user, CARD_ID) 333 | opted_in = True 334 | elif optin_choice.lower() == "n": 335 | opted_in = True 336 | 337 | amount = int(args[""]) 338 | 339 | print(f"✏️ Placing order of: {util.microalgos_to_algos(amount)} ALGO\n") 340 | 341 | seller_address = query.current_owner( 342 | indexer_client, CARD_ID, ALGOREALM_CARD_FIRST_BLOCK 343 | ) 344 | 345 | trade_gtxn = actions.card_order( 346 | client=algod_client, 347 | card_contract=card_contract, 348 | buyer=user, 349 | seller_address=seller_address, 350 | royalty_collector_1_addr=ROYALTY_COLLECTOR_1, 351 | royalty_collector_2_addr=ROYALTY_COLLECTOR_2, 352 | price=amount, 353 | ) 354 | print("📝 Partially signed trade group transaction saved as: 'trade.gtxn'\n") 355 | 356 | if args["--notify"]: 357 | print("✉️ Sending buy order notification to the Seller...\n") 358 | result = actions.notify(algod_client, user, seller_address, trade_gtxn) 359 | tx_id = result.tx_ids[0] 360 | print("\n📄 Buy order notification:\n" "https://algoexplorer.io/tx/" + tx_id) 361 | return print("📦 Send `trade.gtxn` file to the Seller to finalize the trade!\n") 362 | 363 | if args["verify-order"]: 364 | card_contract = actions.get_contract_account(CARD_CONTRACT_BYTECODE) 365 | trade_gtxn = retrieve_from_file("trade.gtxn") 366 | 367 | verified_buy_order = actions.verify_buy_order( 368 | card_contract=card_contract, 369 | seller_address=args[""], 370 | royalty_collector_1_addr=ROYALTY_COLLECTOR_1, 371 | royalty_collector_2_addr=ROYALTY_COLLECTOR_2, 372 | trade_gtxn=trade_gtxn, 373 | ) 374 | return print(actions.order_summary(algod_client, verified_buy_order)) 375 | 376 | if args["sell-card"]: 377 | trade_gtxn = retrieve_from_file("trade.gtxn") 378 | 379 | print( 380 | f"🤝 Selling the AlgoRealm Special Card for " 381 | f"{trade_gtxn[2].transaction.amt / 10 ** 6} ALGO:\n" 382 | ) 383 | 384 | user = actions.get_user() 385 | 386 | try: 387 | return actions.sell_card(algod_client, user, trade_gtxn) 388 | except AlgodHTTPError: 389 | quit("You must hold the 👑 Crown and the 🪄 Scepter to sell the Card!\n") 390 | 391 | else: 392 | quit("\nError: read AlgoRealm '--help'!\n") 393 | 394 | 395 | if __name__ == "__main__": 396 | main() 397 | -------------------------------------------------------------------------------- /src/const.py: -------------------------------------------------------------------------------- 1 | # NETWORK 2 | HEADER = {"User-Agent": "algosdk"} 3 | 4 | ALGOD_ADDRESS = "https://mainnet-api.algonode.cloud" 5 | INDEXER_ADDRESS = "https://mainnet-idx.algonode.cloud" 6 | 7 | TEST_ALGOD_ADDRESS = "https://testnet-api.algonode.cloud" 8 | TEST_INDEXER_ADDRESS = "https://testnet-idx.algonode.cloud" 9 | 10 | MAX_CONNECTION_ATTEMPTS = 10 11 | CONNECTION_ATTEMPT_DELAY_SEC = 2 12 | 13 | 14 | # ADDRESSES 15 | REWARDS_POOL = "737777777777777777777777777777777777777777777777777UFEJ2CI" 16 | 17 | # ALGOREAM 18 | ALGOREALM_FIRST_BLOCK = 13578170 19 | ALGOREALM_APP_ID = 137491307 20 | CROWN_ID = 137493252 21 | SCEPTRE_ID = 137494385 22 | ALGOREALM_LAW_BYTECODE = ( 23 | "AiAIAwbr5sdBAQSE9sdB8f7HQegHJgEg/v////////////////////////////////////" 24 | "////8yBCISMwAQIxIzABgkEhAQMwEQJRIzAQAzAAASEDMBBygSEBAzAhAhBBIzAhQzAQAS" 25 | "EDMCESEFEjMCESEGEhEQMwISJRIQMwIBIQcOEDMCFTIDEhAzAiAyAxIQEA==" 26 | ) 27 | 28 | # TEST ALGOREALM 29 | TEST_ALGOREALM_FIRST_BLOCK = 14739865 30 | TEST_ALGOREALM_APP_ID = 16258432 31 | TEST_CROWN_ID = 16258490 32 | TEST_SCEPTRE_ID = 16258497 33 | TEST_ALGOREALM_LAW_BYTECODE = ( 34 | "AiAIAwaAq+AHAQS6q+AHwavgB+gHJgEg/v////////////////////////////////////" 35 | "////8yBCISMwAQIxIQMwAYJBIQMwEQJRIQMwAAMwEAEhAzAQcoEhAzAhAhBBIQMwIUMwEA" 36 | "EhAzAhEhBRIzAhEhBhIREDMCEiUSEDMCASEHDhAzAhUyAxIQMwIgMgMSEA==" 37 | ) 38 | 39 | # ALGOREALM CARD 40 | ROYALTY_PERC = 5 41 | ALGOREALM_CARD_FIRST_BLOCK = 16250000 42 | CARD_ID = 321172366 43 | ROYALTY_COLLECTOR_1 = "H7N65NZIWBOKFDSRNPLLDGN72HVFKXT4RRSY7M66B6Y2PFLQFKLPLHU5JU" 44 | ROYALTY_COLLECTOR_2 = "2PDM3E7WLVPMEKCCMNTHM3FCZNZM4CSJQUOC4SWHMFPAR3N4NXBLCQKHPE" 45 | CARD_CONTRACT_BYTECODE = ( 46 | "AyAOAQMGBOgHnq6WmQGE9sdB8f7HQQVkjueSmQGQ6d8HAM7i0wcmAwtBc2FBbW91bnRFcS" 47 | "A/2+63KLBcoo5Ra9axmb/R6lVefIxlj7PeD7GnlXAqliDTxs2T9l1ewihCY2Z2bKLLcs4K" 48 | "SYUcLkrHYV4I7bxtwjIEIhJAAaIyBCMSQAD4MgQkEkAAAQAzABAkEjMBECQSEDMCECISED" 49 | "MDECISEDMEECISEDMFECUSEDMFASEEDhAzBSAyAxIQMwUVMgMSEDMAGCEFEjcAGgAoEhA3" 50 | "ABwBMwAAEhA3ADAAIQYSEDcAGgEiFhIQEDMBGCEFEjcBGgAoEhA3ARwBMwEAEhA3ATAAIQ" 51 | "cSEDcBGgEiFhIQEDMAADMCBxIQMwEAMwIHEhAzAgAzBRQSEDMDADMCBxIQMwMHKRIQMwQA" 52 | "MwIHEhAzBAcqEhAzAwgzBAgSEDMDCDMCCCEICyEJCg8QMwURIQoSEDMFEiISEDMFEzMCBx" 53 | "IQMwUUMwIAEhBCANczABAkEjMBECQSEDMCECUSEDMCASEEDhAzAiAyAxIQMwIVMgMSEDMA" 54 | "GCEFEjcAGgAoEhA3ABwBMwAAEhA3ADAAIQYSEDcAGgEiFhIQEDMBGCEFEjcBGgAoEhA3AR" 55 | "wBMwEAEhA3ATAAIQcSEDcBGgEiFhIQEDMAADMCFBIQMwEAMwIUEhAzAgIhCw0QMwIRIQoS" 56 | "EDMCEiISEDMCADMCExIQQgA0MRAlEjEBIQQOEDETMgMSEDEVMgMSEDEgMgMSEDERIQoSED" 57 | "ESIQwSEDEAMRQSEDEEIQ0MEA==" 58 | ) 59 | 60 | # TEST ALGOREALM CARD 61 | TEST_ROYALTY_COLLECTOR_1 = "KKXHB5C4QMGQJ4WZQRRXGD7SJF5AKUUJ635U3SNBYKA6IILSJ6YVX2NQQU" 62 | TEST_ROYALTY_COLLECTOR_2 = "HNVDQHZHQ76PDA7VQ54HFFHIYNNYTHZWJSSQVKNWMIDTDPUH7ME5W6CKIE" 63 | 64 | # ASA STATE OBSERVER 65 | ASA_STATE_OBSERVER_APP_ID = 321230622 66 | 67 | # TEST ASA STATE OBSERVER 68 | TEST_ASA_STATE_OBSERVER_APP_ID = 24123396 69 | -------------------------------------------------------------------------------- /src/contracts/README.md: -------------------------------------------------------------------------------- 1 | # AlgoRealm Depoloyment 2 | 3 | The following procedure describe the AlgoRealm depoloyment process, which could 4 | be useful in case of testing purposes. The deployment process consists in 4 5 | steps: three for the artifacts creation and one for the artifacts binding. 6 | 7 | ### 1. AlgoRealm Test ASAs 8 | 9 | #### Crown of Test 10 | 11 | Creates the *Crown of Test* ASA with: 12 | 13 | - Name: `Crown of Test` 14 | - Unit Name: `CROWN` 15 | - Total: `1` 16 | - Decimals: `0` 17 | - `manager` and the `clawback` addresses active; 18 | 19 | Get the `TEST_CROWN_ID`. 20 | 21 | #### Sceptre of Test 22 | 23 | Creates the *Sceptre of Test* ASA with: 24 | 25 | - Name: `Sceptre of Test` 26 | - Unit Name: `SCEPTRE` 27 | - Total: `1` 28 | - Decimals: `0` 29 | - `manager` and the `clawback` addresses active; 30 | 31 | Get the `TEST_SCEPTRE_ID`. 32 | 33 | ### 2. AlgoRealm Test App 34 | 35 | Depoloy the *AlgoRealm Test App* with: 36 | 37 | - Approval Program: `algorealm_approval.teal` (compiling TEAL to AVM bytecode); 38 | - Clear Program: `algorealm_clear.teal` (compiling TEAL to AVM bytecode); 39 | - Global Ints: `2`; 40 | - Global Bytes: `3`; 41 | - Local Ints: `0`; 42 | - Local Bytes: `0`; 43 | 44 | Get the `TEST_ALGOREALM_APP_ID`. 45 | 46 | ### 3. AlgoRealm Test Law 47 | 48 | Replace the `TMPL_` parameters in the `tmpl_algorealm_law.teal` Smart Signature 49 | with the `TEST_` IDs obtained in the previous steps, specifically: 50 | 51 | 1. The `TEST_ALGOREALM_APP_ID` here: 52 | 53 | ```teal 54 | // To the AlgoRealm App 55 | gtxn 0 ApplicationID 56 | int TMPL_ALGOREALM_APP_ID 57 | ``` 58 | 59 | 2. The `TEST_CROWN_ID` and the `TEST_SCEPTRE_ID` here: 60 | 61 | ```teal 62 | // Either of the Crown of Entropy 63 | gtxn 2 XferAsset 64 | int TMPL_CROWN_ASA_ID 65 | == 66 | // Or of the Sceptre of Proof 67 | gtxn 2 XferAsset 68 | int TMPL_SCEPTRE_ASA_ID 69 | ``` 70 | 71 | Save the updated TEAL source code as `algorealm_test_law.teal` and then compile 72 | it to obtain the public key `TEST_ALGOREALM_ADDR` associated to the Smart 73 | Signature. 74 | 75 | Fund the `TEST_ALGOREALM_ADDR` with some ALGOs. 76 | 77 | ## 4. Bindings 78 | 79 | Once all the artifacts are ready: 80 | 81 | - `TEST_CROWN_ID`; 82 | - `TEST_SCEPTRE_ID`; 83 | - `TEST_ALGOREALM_APP_ID`; 84 | - `TEST_ALGOREALM_ADDR`; 85 | 86 | They must be **binded** as follows: 87 | 88 | 1. As a `manager`, set `TEST_ALGOREALM_ADDR` as `clawback` address of `TEST_CROWN_ID`; 89 | 2. As a `manager`, set `TEST_ALGOREALM_ADDR` as `clawback` address of `TEST_SCEPTRE_ID`; 90 | 3. Call the `TEST_ALGOREALM_APP_ID` passing the `TEST_ALGOREALM_ADDR` as first argument of the AppCall. 91 | 92 | A new testing instance of AlgoRealm is now succesfully depolyed! 93 | -------------------------------------------------------------------------------- /src/contracts/algorealm_approval.teal: -------------------------------------------------------------------------------- 1 | // AlgoRealm Application 2 | #pragma version 2 3 | 4 | // If is an App Creation 5 | txn ApplicationID 6 | int 0 7 | == 8 | // Then jump to `algorealm_creation` 9 | bnz algorealm_creation 10 | 11 | // Else jump to `algorealm` 12 | txn OnCompletion 13 | int NoOp 14 | == 15 | bnz algorealm 16 | 17 | // Reject in any other case 18 | int 0 19 | return 20 | 21 | algorealm_creation: 22 | // Initialize the `RandomicMajestyOfAlgorand` 23 | byte "RandomicMajestyOfAlgorand" 24 | byte "Silvio" 25 | app_global_put 26 | 27 | // Initialize the `VerifiableMajestyOfAlgorand` 28 | byte "VerifiableMajestyOfAlgorand" 29 | byte "Silvio" 30 | app_global_put 31 | 32 | // Initialize the `CrownOfEntropyDonation` 33 | byte "CrownOfEntropyDonation" 34 | int 0 35 | app_global_put 36 | 37 | // Initialize the `SceptreOfProofDonation` 38 | byte "SceptreOfProofDonation" 39 | int 0 40 | app_global_put 41 | 42 | // Approve 43 | int 1 44 | return 45 | b end_algorealm 46 | 47 | algorealm: 48 | // If is a single txn 49 | global GroupSize 50 | int 1 51 | == 52 | // Then jump to `algorealm_law` setup 53 | bnz algorealm_law 54 | 55 | // Else if a groupo of 3 txns 56 | global GroupSize 57 | int 3 58 | == 59 | // Then jump to `algorealm_claims` 60 | bnz algorealm_claims 61 | 62 | // Reject in any other case 63 | int 0 64 | return 65 | b end_algorealm 66 | 67 | algorealm_law: 68 | // Check if AlgoRealmLaw exists 69 | int 0 70 | byte "AlgoRealmLaw" 71 | app_global_get_ex 72 | store 0 73 | store 1 74 | 75 | // If AlgoRealmLaw has been already promulgated 76 | load 0 77 | // Then jump to `promulgate_law_failure` 78 | bnz promulgate_law_failure 79 | 80 | // Else promulgate the AlgoRealmLaw 81 | byte "AlgoRealmLaw" 82 | txna ApplicationArgs 0 83 | app_global_put 84 | b promulgate_law 85 | 86 | promulgate_law_failure: 87 | // Reject 88 | int 0 89 | return 90 | b end_algorealm 91 | 92 | promulgate_law: 93 | // Approve 94 | int 1 95 | return 96 | b end_algorealm 97 | 98 | algorealm_claims: 99 | // First txn must be an AppCall with 2 args 100 | gtxn 0 TypeEnum 101 | int appl 102 | == 103 | gtxn 0 NumAppArgs 104 | int 2 105 | == 106 | && 107 | 108 | // Second txn must be a Payment donation 109 | gtxn 1 TypeEnum 110 | int pay 111 | == 112 | && 113 | 114 | // Third txn must be an NFT AssetTransfer executed by the AlgoRealmLaw 115 | gtxn 2 TypeEnum 116 | int axfer 117 | == 118 | && 119 | gtxn 2 Sender 120 | byte "AlgoRealmLaw" 121 | app_global_get 122 | == 123 | && 124 | 125 | // If so, jump to `claims` 126 | bnz claims 127 | 128 | // Reject in any other case 129 | int 0 130 | return 131 | b end_algorealm 132 | 133 | claims: 134 | // If first arg is "Crown" 135 | gtxna 0 ApplicationArgs 0 136 | byte "Crown" 137 | == 138 | // The jump to `claim_crown` 139 | bnz claim_crown 140 | 141 | // If first arg is "Sceptre" 142 | gtxna 0 ApplicationArgs 0 143 | byte "Sceptre" 144 | == 145 | // The jump to `claim_sceptre` 146 | bnz claim_sceptre 147 | 148 | // Reject in any other case 149 | int 0 150 | return 151 | b end_algorealm 152 | 153 | claim_crown: 154 | // If `CrownOfEntropyDonation` is greater than previous one 155 | gtxn 1 Amount 156 | byte "CrownOfEntropyDonation" 157 | app_global_get 158 | > 159 | // Then jump to `randomic_majesty` 160 | bnz randomic_majesty 161 | 162 | // Else reject 163 | int 0 164 | return 165 | b end_algorealm 166 | 167 | randomic_majesty: 168 | // Set the `RandomicMajestyOfAlgorand` name to second args 169 | byte "RandomicMajestyOfAlgorand" 170 | gtxna 0 ApplicationArgs 1 171 | app_global_put 172 | // Set the `CrownOfEntropyDonation` to current donation 173 | byte "CrownOfEntropyDonation" 174 | gtxn 1 Amount 175 | app_global_put 176 | // Approve 177 | int 1 178 | return 179 | b end_algorealm 180 | 181 | claim_sceptre: 182 | // If `SceptreOfProofDonation` is greater than previous one 183 | gtxn 1 Amount 184 | byte "SceptreOfProofDonation" 185 | app_global_get 186 | > 187 | // Then jump to `verifiable_majesty` 188 | bnz verifiable_majesty 189 | 190 | // Else reject 191 | int 0 192 | return 193 | b end_algorealm 194 | 195 | verifiable_majesty: 196 | // Set the `VerifiableMajestyOfAlgorand` name to second args 197 | byte "VerifiableMajestyOfAlgorand" 198 | gtxna 0 ApplicationArgs 1 199 | app_global_put 200 | // Set the `SceptreOfProofDonation` to current donation 201 | byte "SceptreOfProofDonation" 202 | gtxn 1 Amount 203 | app_global_put 204 | // Approve 205 | int 1 206 | return 207 | 208 | end_algorealm: 209 | -------------------------------------------------------------------------------- /src/contracts/algorealm_clear.teal: -------------------------------------------------------------------------------- 1 | // AlgoRealm Application 2 | #pragma version 2 3 | // Approve 4 | int 1 5 | -------------------------------------------------------------------------------- /src/contracts/algorealm_law.teal: -------------------------------------------------------------------------------- 1 | // AlgoRealm Law 2 | #pragma version 2 3 | 4 | // Must be a Group of 3 txns 5 | global GroupSize 6 | int 3 7 | == 8 | 9 | // First txn must be an AppCall 10 | gtxn 0 TypeEnum 11 | int appl 12 | == 13 | // To the AlgoRealm App 14 | gtxn 0 ApplicationID 15 | int 137491307 16 | == 17 | && 18 | && 19 | 20 | // Second txn must be a Payment donation 21 | gtxn 1 TypeEnum 22 | int pay 23 | == 24 | // From the AlgoRealm App caller 25 | gtxn 1 Sender 26 | gtxn 0 Sender 27 | == 28 | && 29 | // To the Rewards Pool 30 | gtxn 1 Receiver 31 | addr 737777777777777777777777777777777777777777777777777UFEJ2CI 32 | == 33 | && 34 | && 35 | 36 | // Third txn must be the NFT AssetTransfer 37 | gtxn 2 TypeEnum 38 | int axfer 39 | == 40 | // To the AlgoRealm donor 41 | gtxn 2 AssetReceiver 42 | gtxn 1 Sender 43 | == 44 | && 45 | // Either of the Crown of Entropy 46 | gtxn 2 XferAsset 47 | int 137493252 48 | == 49 | // Or of the Sceptre of Proof 50 | gtxn 2 XferAsset 51 | int 137494385 52 | == 53 | || 54 | && 55 | // The transfer is of a unique NFT 56 | gtxn 2 AssetAmount 57 | int 1 58 | == 59 | && 60 | // Paying at most 1000 microAlgos as txn fee 61 | gtxn 2 Fee 62 | int 1000 63 | <= 64 | && 65 | // With no AssetCloseTo 66 | gtxn 2 AssetCloseTo 67 | global ZeroAddress 68 | == 69 | && 70 | // With no RekeyTo 71 | gtxn 2 RekeyTo 72 | global ZeroAddress 73 | == 74 | && 75 | && 76 | -------------------------------------------------------------------------------- /src/contracts/card/README.md: -------------------------------------------------------------------------------- 1 | # AlgoRealm Card on AlgoWorld NFT 2 | 3 | The [AlgoRealm Card](https://algoexplorer.io/asset/321172366) is a unique [AlgoWorld NFT](https://algoworld.io/) Special Card, securely stored in an enchanted coffer. 4 | 5 | 6 |
7 | 8 | algorealm_card 9 | 10 |
11 | 12 | 13 | Only the generous heart of the [Great Majesty of Algorand](https://github.com/cusma/algorealm) will ever able to break the spell, claim the **unique Special Card** and trade it! 14 | 15 | 16 | 17 | ## How to claim the AlgoRealm Card? 18 | 19 | ### Role: Card Claimer 20 | As **Card Claimer**, you previously need to conquer both the [Crown of Entropy](https://github.com/cusma/algorealm#claim-the-crown-of-entropy) and the [Sceptre of Proof](https://github.com/cusma/algorealm#claim-the-sceptre-of-proof), ascending to [AlgoRealm's throne](https://algoexplorer.io/application/137491307). 21 | 22 | The AlgoRealm Card can be claimed **starting from block 16,250,000**: hold strong both the Crown and the Sceptre and keep the throne until there! 23 | 24 | #### Card Claim's Unsigned Transactions 25 | **Save** the [`algorealm_card_contract.teal`](https://github.com/cusma/algorealm/blob/algorealm_card/card/algorealm_card_contract.teal) into your node directory. 26 | 27 | 1. Wear the Crown of Entropy 28 | ```shell 29 | ./goal app call 30 | --app-id 321230622 31 | --from YOUR_ADDRESS 32 | --app-account YOUR_ADDRESS 33 | --app-arg "str:AsaAmountEq" 34 | --app-arg "int:1" 35 | --foreign-asset 137493252 36 | --out proof_crown.txn 37 | ``` 38 | 39 | 2. Brandish the Sceptre of Proof 40 | ```shell 41 | ./goal app call 42 | --app-id 321230622 43 | --from YOUR_ADDRESS 44 | --app-account YOUR_ADDRESS 45 | --app-arg "str:AsaAmountEq" 46 | --app-arg "int:1" 47 | --foreign-asset 137494385 48 | --out proof_sceptre.txn 49 | ``` 50 | 51 | 3. Claim the AlgoRealm Special Card 52 | ```shell 53 | ./goal asset send 54 | --from ZTQNLH7QZ3J5ZDRS5VFBICPLOEFOGPNK5D6GV4VXAFPOCZJMMCBIRDSBWU 55 | --to YOUR_ADDRESS 56 | --assetid 321172366 57 | --amount 1 58 | --clawback ZTQNLH7QZ3J5ZDRS5VFBICPLOEFOGPNK5D6GV4VXAFPOCZJMMCBIRDSBWU 59 | --out transfer_card.txn 60 | ``` 61 | 62 | #### Create the Unsigned Claim Group Transaction 63 | ```shell 64 | cat proof_crown.txn proof_sceptre.txn transfer_card.txn > claim.txn 65 | ``` 66 | ```shell 67 | ./goal clerk group -i claim.txn -o claim.gtxn 68 | ``` 69 | 70 | #### Split the Unsigned Claim Group Transaction 71 | ```shell 72 | ./goal clerk split -i claim.gtxn -o unsigned_claim.txn 73 | ``` 74 | 75 | #### Sign the Claim Group Transaction 76 | ```shell 77 | ./goal clerk sign -i unsigned_claim-0.txn -o claim-0.stxn 78 | ``` 79 | ```shell 80 | ./goal clerk sign -i unsigned_claim-1.txn -o claim-1.stxn 81 | ``` 82 | ```shell 83 | ./goal clerk sign -i unsigned_claim-2.txn -p algorealm_card_contract.teal -o claim-2.stxn 84 | ``` 85 | 86 | #### Submit the Claim Group Transaction 87 | ```shell 88 | cat claim-0.stxn claim-1.stxn claim-2.stxn > claim.sgtxn 89 | ``` 90 | ```shell 91 | ./goal clerk rawsend -f claim.sgtxn 92 | ``` 93 | 94 | 95 | 96 | ## How to trade the AlgoRealm Card? 97 | 98 | The AlgoRealm Card can be traded between a **Seller** and a **Buyer**, for ALGO, at a price freely agreed between the parties. AlgoRealm and AlgoWorld NFT both collect a 5% royalty on the trades. 99 | 100 | As **Seller**, you can sell your AlgoRealm Card if and only if [you still sit on AlgoRealm's throne](https://algoexplorer.io/application/137491307), both as Randomic Majesty of Algorand and Verifiable Majesty of Algorand. 101 | 102 | So, if you have been dethroned, you need to conquer again both the [Crow of Entropy](https://algoexplorer.io/asset/137493252) and the [Sceptre of Proof](https://algoexplorer.io/asset/137494385) to be able to sell your AlgoRealm Card. 103 | 104 | As **Buyer**, you can submit a buy order proposal to the **Seller**, as *Unsigned Trade Group Transaction*, without any counterparty risk. As **Seller** you will only sign the buy order proposal after your personal review of the *Unsigned Trade Group Transaction*, sent by the **Buyer** and submit it to the blockchain. 105 | 106 | 107 | 108 | ### Trading role: Buyer 109 | 110 | As **Buyer** you must first Opt-In the AlgoRealm Card. 111 | 112 | #### Opt-In AlgoRealm Special Card 113 | 114 | ```shell 115 | ./goal asset send 116 | --from BUYER_ADDRESS 117 | --to BUYER_ADDRESS 118 | --assetid 321172366 119 | --amount 0 120 | ``` 121 | 122 | #### Placing buy orders as Unsigned Trade Group Transaction 123 | 124 | As **Buyer** you create a buy order proposal as *Unsigned Trade Group Transaction* of 6 transactions, to be reviewed an eventually signed by the **Seller**. 125 | 126 | To generate a buy order proposal you need to fill in the following parameters: 127 | 128 | 1. `BUYER_ADDRESS`: your address 129 | 2. `SELLER_ADDRESS`: the address of the current owner of the AlgoRealm Card 130 | 3. `MICROALGO_ORDER_PRICE`: the value, in microALGOs, of your buy order proposal 131 | 4. `5%_MICROALGO_ORDER_PRICE:`: the value, in microALGOs, of the royalty (equal to the 5% of buy order's value, rounded up) 132 | 133 | #### Write Unsigned Card Trade's Standalone Transactions 134 | 135 | 1. Proof of ownership: the **Seller** must own the Crown of Entropy 136 | 137 | ```shell 138 | ./goal app call 139 | --app-id 321230622 140 | --from SELLER_ADDRESS 141 | --app-account SELLER_ADDRESS 142 | --app-arg "str:AsaAmountEq" 143 | --app-arg "int:1" 144 | --foreign-asset 137493252 145 | --out proof_crown.txn 146 | ``` 147 | 148 | 2. Proof of ownership: the **Seller** must own the Sceptre of Proof 149 | 150 | ```shell 151 | ./goal app call 152 | --app-id 321230622 153 | --from SELLER_ADDRESS 154 | --app-account SELLER_ADDRESS 155 | --app-arg "str:AsaAmountEq" 156 | --app-arg "int:1" 157 | --foreign-asset 137494385 158 | --out proof_sceptre.txn 159 | ``` 160 | 161 | 3. Buy order proposal 162 | 163 | ```shell 164 | ./goal clerk send 165 | --from BUYER_ADDRESS 166 | --to SELLER_ADDRESS 167 | --amount MICROALGO_ORDER_PRICE 168 | --out buy_order_proposal.txn 169 | ``` 170 | 171 | 4. Royalty Payment to Rightholder 1 172 | 173 | ```shell 174 | ./goal clerk send 175 | --from SELLER_ADDRESS 176 | --to H7N65NZIWBOKFDSRNPLLDGN72HVFKXT4RRSY7M66B6Y2PFLQFKLPLHU5JU 177 | --amount 5%_MICROALGO_ORDER_PRICE 178 | --out royalty_1.txn 179 | ``` 180 | 181 | 5. Royalty Payment to Rightholder 2 182 | 183 | ```shell 184 | ./goal clerk send 185 | --from SELLER_ADDRESS 186 | --to 2PDM3E7WLVPMEKCCMNTHM3FCZNZM4CSJQUOC4SWHMFPAR3N4NXBLCQKHPE 187 | --amount 5%_MICROALGO_ORDER_PRICE 188 | --out royalty_2.txn 189 | ``` 190 | 191 | 6. AlgoRealm Card Transfer 192 | 193 | ```shell 194 | ./goal asset send 195 | --from SELLER_ADDRESS 196 | --to BUYER_ADDRESS 197 | --assetid 321172366 198 | --amount 1 199 | --clawback ZTQNLH7QZ3J5ZDRS5VFBICPLOEFOGPNK5D6GV4VXAFPOCZJMMCBIRDSBWU 200 | --out transfer_card.txn 201 | ``` 202 | 203 | #### Create Unsigned Trade Group Transaction 204 | 205 | ```shell 206 | cat proof_crown.txn proof_sceptre.txn buy_order_proposal.txn royalty_1.txn royalty_2.txn transfer_card.txn > trade.txn 207 | ``` 208 | 209 | ```shell 210 | ./goal clerk group -i trade.txn -o trade.gtxn 211 | ``` 212 | 213 | #### Split the Unsigned Trade Group Transaction 214 | 215 | ```shell 216 | ./goal clerk split -i trade.gtxn -o unsigned_trade.txn 217 | ``` 218 | 219 | #### Sign just your Buy Order Proposal Transaction 220 | 221 | ```shell 222 | ./goal clerk sign -i unsigned_trade-2.txn -o trade-2.stxn 223 | ``` 224 | 225 | As **Buyer**, you will send both the unsigned `trade.gtxn` and the signed `trade-2.stxn` to the **Seller**. 226 | 227 | Note that you have no counterparty risk, since `trade-2.stxn` can only be approved by the consensus protocol as part of the whole *Trade Group Transaction* you just created. 228 | 229 | 230 | 231 | ### Trading role: Seller 232 | 233 | As **Seller**, you review the buy order proposal placed by the **Buyer** as *Unsigned Trade Group Transaction*. If you agree on the buy order proposal, you sign the AlgoRealm Card *Unsigned Trade Group Transaction* and submit it to the blockchain. 234 | 235 | Remember that, in doing so, there is no counterparty risk for both parties. 236 | 237 | Before being able to sign the rest of the trade you must **save** the [`algorealm_card_contract.teal`](https://github.com/cusma/algorealm/blob/algorealm_card/card/algorealm_card_contract.teal) into your node directory. 238 | 239 | #### Inspect the Unsigned Trade Group Transaction 240 | 241 | As **Seller**, you need to inspect the `trade.gtxn`, verifying the transactions' details proposed by the **Buyer**. Note that you must be able to autonomously verify the *Unsigned Trade Group Transaction* correctness. 242 | 243 | ```shell 244 | ./goal clerk inspect trade.gtxn 245 | ``` 246 | 247 | As **Seller**, if you approve the *Unsigned Trade Group Transaction*, you can complete the signing process of the *Trade Group Transaction* and submit it to the blockchain. 248 | 249 | #### Split the Unsigned Trade Group Transaction 250 | 251 | ```shell 252 | ./goal clerk split -i trade.gtxn -o unsigned_trade.txn 253 | ``` 254 | 255 | #### Sign rest of Trade Transactions 256 | ```shell 257 | ./goal clerk sign -i unsigned_trade-0.txn -o trade-0.stxn 258 | ``` 259 | ```shell 260 | ./goal clerk sign -i unsigned_trade-1.txn -o trade-1.stxn 261 | ``` 262 | As **Seller**, you do not sign the `unsigned_trade-2.txn` (already signed and sent by the **Buyer** as `trade-2.stxn`). 263 | 264 | ```shell 265 | ./goal clerk sign -i unsigned_trade-3.txn -o trade-3.stxn 266 | ``` 267 | ```shell 268 | ./goal clerk sign -i unsigned_trade-4.txn -o trade-4.stxn 269 | ``` 270 | ```shell 271 | ./goal clerk sign -i unsigned_trade-5.txn -p algorealm_card_contract.teal -o trade-5.stxn 272 | ``` 273 | 274 | #### Submit the Trade Group Transaction 275 | ```shell 276 | cat trade-0.stxn trade-1.stxn trade-2.stxn trade-3.stxn trade-4.stxn trade-5.stxn > trade.sgtxn 277 | ``` 278 | ```shell 279 | ./goal clerk rawsend -f trade.sgtxn 280 | ``` 281 | -------------------------------------------------------------------------------- /src/contracts/card/algorealm_card_contract.teal: -------------------------------------------------------------------------------- 1 | #pragma version 3 2 | global GroupSize 3 | int 1 4 | == 5 | bnz l6 6 | global GroupSize 7 | int 3 8 | == 9 | bnz l5 10 | global GroupSize 11 | int 6 12 | == 13 | bnz l4 14 | err 15 | l4: 16 | gtxn 0 TypeEnum 17 | int appl 18 | == 19 | gtxn 1 TypeEnum 20 | int appl 21 | == 22 | && 23 | gtxn 2 TypeEnum 24 | int pay 25 | == 26 | && 27 | gtxn 3 TypeEnum 28 | int pay 29 | == 30 | && 31 | gtxn 4 TypeEnum 32 | int pay 33 | == 34 | && 35 | gtxn 5 TypeEnum 36 | int axfer 37 | == 38 | && 39 | gtxn 5 Fee 40 | int 1000 41 | <= 42 | && 43 | gtxn 5 RekeyTo 44 | global ZeroAddress 45 | == 46 | && 47 | gtxn 5 AssetCloseTo 48 | global ZeroAddress 49 | == 50 | && 51 | gtxn 0 ApplicationID 52 | int 321230622 53 | == 54 | gtxna 0 ApplicationArgs 0 55 | byte "AsaAmountEq" 56 | == 57 | && 58 | gtxna 0 Accounts 1 59 | gtxn 0 Sender 60 | == 61 | && 62 | gtxna 0 Assets 0 63 | int 137493252 64 | == 65 | && 66 | gtxna 0 ApplicationArgs 1 67 | int 1 68 | itob 69 | == 70 | && 71 | && 72 | gtxn 1 ApplicationID 73 | int 321230622 74 | == 75 | gtxna 1 ApplicationArgs 0 76 | byte "AsaAmountEq" 77 | == 78 | && 79 | gtxna 1 Accounts 1 80 | gtxn 1 Sender 81 | == 82 | && 83 | gtxna 1 Assets 0 84 | int 137494385 85 | == 86 | && 87 | gtxna 1 ApplicationArgs 1 88 | int 1 89 | itob 90 | == 91 | && 92 | && 93 | gtxn 0 Sender 94 | gtxn 2 Receiver 95 | == 96 | && 97 | gtxn 1 Sender 98 | gtxn 2 Receiver 99 | == 100 | && 101 | gtxn 2 Sender 102 | gtxn 5 AssetReceiver 103 | == 104 | && 105 | gtxn 3 Sender 106 | gtxn 2 Receiver 107 | == 108 | && 109 | gtxn 3 Receiver 110 | addr H7N65NZIWBOKFDSRNPLLDGN72HVFKXT4RRSY7M66B6Y2PFLQFKLPLHU5JU 111 | == 112 | && 113 | gtxn 4 Sender 114 | gtxn 2 Receiver 115 | == 116 | && 117 | gtxn 4 Receiver 118 | addr 2PDM3E7WLVPMEKCCMNTHM3FCZNZM4CSJQUOC4SWHMFPAR3N4NXBLCQKHPE 119 | == 120 | && 121 | gtxn 3 Amount 122 | gtxn 4 Amount 123 | == 124 | && 125 | gtxn 3 Amount 126 | gtxn 2 Amount 127 | int 5 128 | * 129 | int 100 130 | / 131 | >= 132 | && 133 | gtxn 5 XferAsset 134 | int 321172366 135 | == 136 | && 137 | gtxn 5 AssetAmount 138 | int 1 139 | == 140 | && 141 | gtxn 5 AssetSender 142 | gtxn 2 Receiver 143 | == 144 | && 145 | gtxn 5 AssetReceiver 146 | gtxn 2 Sender 147 | == 148 | && 149 | b l7 150 | l5: 151 | gtxn 0 TypeEnum 152 | int appl 153 | == 154 | gtxn 1 TypeEnum 155 | int appl 156 | == 157 | && 158 | gtxn 2 TypeEnum 159 | int axfer 160 | == 161 | && 162 | gtxn 2 Fee 163 | int 1000 164 | <= 165 | && 166 | gtxn 2 RekeyTo 167 | global ZeroAddress 168 | == 169 | && 170 | gtxn 2 AssetCloseTo 171 | global ZeroAddress 172 | == 173 | && 174 | gtxn 0 ApplicationID 175 | int 321230622 176 | == 177 | gtxna 0 ApplicationArgs 0 178 | byte "AsaAmountEq" 179 | == 180 | && 181 | gtxna 0 Accounts 1 182 | gtxn 0 Sender 183 | == 184 | && 185 | gtxna 0 Assets 0 186 | int 137493252 187 | == 188 | && 189 | gtxna 0 ApplicationArgs 1 190 | int 1 191 | itob 192 | == 193 | && 194 | && 195 | gtxn 1 ApplicationID 196 | int 321230622 197 | == 198 | gtxna 1 ApplicationArgs 0 199 | byte "AsaAmountEq" 200 | == 201 | && 202 | gtxna 1 Accounts 1 203 | gtxn 1 Sender 204 | == 205 | && 206 | gtxna 1 Assets 0 207 | int 137494385 208 | == 209 | && 210 | gtxna 1 ApplicationArgs 1 211 | int 1 212 | itob 213 | == 214 | && 215 | && 216 | gtxn 0 Sender 217 | gtxn 2 AssetReceiver 218 | == 219 | && 220 | gtxn 1 Sender 221 | gtxn 2 AssetReceiver 222 | == 223 | && 224 | gtxn 2 FirstValid 225 | int 16250000 226 | > 227 | && 228 | gtxn 2 XferAsset 229 | int 321172366 230 | == 231 | && 232 | gtxn 2 AssetAmount 233 | int 1 234 | == 235 | && 236 | gtxn 2 Sender 237 | gtxn 2 AssetSender 238 | == 239 | && 240 | b l7 241 | l6: 242 | txn TypeEnum 243 | int axfer 244 | == 245 | txn Fee 246 | int 1000 247 | <= 248 | && 249 | txn AssetSender 250 | global ZeroAddress 251 | == 252 | && 253 | txn AssetCloseTo 254 | global ZeroAddress 255 | == 256 | && 257 | txn RekeyTo 258 | global ZeroAddress 259 | == 260 | && 261 | txn XferAsset 262 | int 321172366 263 | == 264 | && 265 | txn AssetAmount 266 | int 0 267 | == 268 | && 269 | txn Sender 270 | txn AssetReceiver 271 | == 272 | && 273 | txn LastValid 274 | int 16052558 275 | < 276 | && 277 | l7: 278 | -------------------------------------------------------------------------------- /src/contracts/tmpl_algorealm_law.teal: -------------------------------------------------------------------------------- 1 | // AlgoRealm Law 2 | #pragma version 2 3 | 4 | // Must be a Group of 3 txns 5 | global GroupSize 6 | int 3 7 | == 8 | 9 | // First txn must be an AppCall 10 | gtxn 0 TypeEnum 11 | int appl 12 | == 13 | // To the AlgoRealm App 14 | gtxn 0 ApplicationID 15 | int TMPL_ALGOREALM_APP_ID 16 | == 17 | && 18 | && 19 | 20 | // Second txn must be a Payment donation 21 | gtxn 1 TypeEnum 22 | int pay 23 | == 24 | // From the AlgoRealm App caller 25 | gtxn 1 Sender 26 | gtxn 0 Sender 27 | == 28 | && 29 | // To the Rewards Pool 30 | gtxn 1 Receiver 31 | addr 737777777777777777777777777777777777777777777777777UFEJ2CI 32 | == 33 | && 34 | && 35 | 36 | // Third txn must be the NFT AssetTransfer 37 | gtxn 2 TypeEnum 38 | int axfer 39 | == 40 | // To the AlgoRealm donor 41 | gtxn 2 AssetReceiver 42 | gtxn 1 Sender 43 | == 44 | && 45 | // Either of the Crown of Entropy 46 | gtxn 2 XferAsset 47 | int TMPL_CROWN_ASA_ID 48 | == 49 | // Or of the Sceptre of Proof 50 | gtxn 2 XferAsset 51 | int TMPL_SCEPTRE_ASA_ID 52 | == 53 | || 54 | && 55 | // The transfer is of a unique NFT 56 | gtxn 2 AssetAmount 57 | int 1 58 | == 59 | && 60 | // Paying at most 1000 microAlgos as txn fee 61 | gtxn 2 Fee 62 | int 1000 63 | <= 64 | && 65 | // With no AssetCloseTo 66 | gtxn 2 AssetCloseTo 67 | global ZeroAddress 68 | == 69 | && 70 | // With no RekeyTo 71 | gtxn 2 RekeyTo 72 | global ZeroAddress 73 | == 74 | && 75 | && 76 | -------------------------------------------------------------------------------- /src/query.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import math 3 | import time 4 | from operator import itemgetter 5 | 6 | from algosdk.error import IndexerHTTPError 7 | from algosdk.v2client.indexer import IndexerClient 8 | 9 | from const import CONNECTION_ATTEMPT_DELAY_SEC, MAX_CONNECTION_ATTEMPTS 10 | 11 | 12 | def algorelm_calls( 13 | client: IndexerClient, 14 | algorealm_app_id: int, 15 | algorealm_first_round: int, 16 | ) -> list: 17 | """ 18 | Search for all the Application Calls to AlgoRealm. 19 | 20 | Args: 21 | client: Algorand Indexer Client 22 | algorealm_app_id: AlgoRealm Application ID 23 | algorealm_first_round: AlgoRealm first round on blockchain 24 | 25 | Returns: 26 | List of all the Application Calls to AlgoRealm. 27 | """ 28 | nexttoken = "" 29 | numtx = 1 30 | calls = [] 31 | while numtx > 0: 32 | result = client.search_transactions( 33 | limit=1000, 34 | next_page=nexttoken, 35 | application_id=algorealm_app_id, 36 | min_round=algorealm_first_round, 37 | ) 38 | calls += result["transactions"] 39 | numtx = len(result["transactions"]) 40 | if numtx > 0: 41 | nexttoken = result["next-token"] 42 | return calls 43 | 44 | 45 | def algorelm_nft_txns( 46 | client: IndexerClient, 47 | nft_id: int, 48 | algorealm_first_round: int, 49 | ) -> list: 50 | """ 51 | Search for all the Asset Transfer transactions involving AlgoRealm NFTs. 52 | Args: 53 | client: Algorand Indexer Client 54 | nft_id: AlgoRealm NFT ID (Crown or Sceptre) 55 | algorealm_first_round: AlgoRealm first round on blockchain 56 | 57 | Returns: 58 | List of all the Asset Transfer transactions involving AlgoRealm NFTs. 59 | """ 60 | nexttoken = "" 61 | numtx = 1 62 | nft_txns = [] 63 | while numtx > 0: 64 | result = client.search_asset_transactions( 65 | asset_id=nft_id, 66 | limit=1000, 67 | next_page=nexttoken, 68 | txn_type="axfer", 69 | min_round=algorealm_first_round, 70 | ) 71 | nft_txns += result["transactions"] 72 | numtx = len(result["transactions"]) 73 | if numtx > 0: 74 | nexttoken = result["next-token"] 75 | return nft_txns 76 | 77 | 78 | def claims_history( 79 | client: IndexerClient, 80 | algorealm_app_id: int, 81 | algorealm_first_round: int, 82 | ) -> list[dict]: 83 | """ 84 | Retrieve the AlgoRealm Majesties claims history from chain. 85 | 86 | Args: 87 | client: Algorand Indexer Client 88 | algorealm_app_id: AlgoRealm Application ID 89 | algorealm_first_round: AlgoRealm first round on blockchain 90 | 91 | Returns: 92 | Historical list of claims. 93 | """ 94 | attempts = 1 95 | calls = None 96 | while attempts <= MAX_CONNECTION_ATTEMPTS: 97 | try: 98 | calls = algorelm_calls( 99 | client=client, 100 | algorealm_app_id=algorealm_app_id, 101 | algorealm_first_round=algorealm_first_round, 102 | ) 103 | break 104 | except IndexerHTTPError: 105 | print( 106 | f"Indexer Client connection attempt " 107 | f"{attempts}/{MAX_CONNECTION_ATTEMPTS}" 108 | ) 109 | print("Trying to contact Indexer Client again...") 110 | time.sleep(CONNECTION_ATTEMPT_DELAY_SEC) 111 | finally: 112 | attempts += 1 113 | if not calls: 114 | quit("❌ Unable to connect to Indexer Client!") 115 | 116 | claims = [] 117 | for call in calls: 118 | call_args = call["application-transaction"]["application-args"] 119 | # Check is an NFT claim call 120 | if len(call_args) == 2: 121 | claim = { 122 | "block": call["confirmed-round"], 123 | "nft": base64.b64decode(call_args[0]).decode(), 124 | "name": base64.b64decode(call_args[1]).decode(), 125 | "donation": call["global-state-delta"][0]["value"]["uint"], 126 | } 127 | if claim["nft"] == "Crown": 128 | claim["symbol"] = "👑" 129 | claim["nft_name"] = "Crown of Entropy" 130 | claim["title"] = "Randomic Majesty" 131 | else: 132 | claim["symbol"] = "🪄" 133 | claim["nft_name"] = "Sceptre of Proof" 134 | claim["title"] = "Verifiable Majesty" 135 | claims += [claim] 136 | return claims 137 | 138 | 139 | def current_owner( 140 | client: IndexerClient, 141 | nft_id: int, 142 | algorealm_first_round: int, 143 | ) -> str: 144 | """ 145 | Retrieve the current owner of an NFT. 146 | 147 | Args: 148 | client: Algorand Indexer Client 149 | nft_id: AlgoRealm NFT (Crown, Sceptre, Card) 150 | algorealm_first_round: AlgoRealm first round on blockchain 151 | 152 | Returns: 153 | Address of current NFT owner. 154 | """ 155 | attempts = 1 156 | nft_txns = None 157 | while attempts <= MAX_CONNECTION_ATTEMPTS: 158 | try: 159 | nft_txns = algorelm_nft_txns( 160 | client=client, 161 | nft_id=nft_id, 162 | algorealm_first_round=algorealm_first_round, 163 | ) 164 | break 165 | except IndexerHTTPError: 166 | print( 167 | f"Indexer Client connection attempt " 168 | f"{attempts}/{MAX_CONNECTION_ATTEMPTS}" 169 | ) 170 | print("Trying to contact Indexer Client again...") 171 | time.sleep(CONNECTION_ATTEMPT_DELAY_SEC) 172 | finally: 173 | attempts += 1 174 | if not nft_txns: 175 | quit("❌ Unable to connect to Indexer Client!") 176 | 177 | nft_txns.reverse() 178 | for txn in nft_txns: 179 | if txn["asset-transfer-transaction"]["amount"] == 1: 180 | return txn["asset-transfer-transaction"]["receiver"] 181 | 182 | 183 | def dynasty(claims: list[dict]) -> list: 184 | majesty_claims = [] 185 | for claim in claims: 186 | majesty = ( 187 | f"{claim['symbol']} {claim['name']} became the {claim['title']} " 188 | f"on Block: {claim['block']}\n" 189 | f"claiming the {claim['nft_name']} with a donation of:\n" 190 | f"{claim['donation']} microALGOs to the Rewards Pool.\n\n" 191 | ) 192 | majesty_claims += [majesty] 193 | return majesty_claims 194 | 195 | 196 | def longevity( 197 | claims: list[dict], latest_block: int, majesty_selector: str 198 | ) -> list[dict]: 199 | assert majesty_selector == "Crown" or majesty_selector == "Sceptre" 200 | 201 | majesty_claims = [] 202 | for claim in claims: 203 | if claim["nft"] == majesty_selector: 204 | majesty_claims += [claim] 205 | 206 | claim_block = [claim["block"] for claim in majesty_claims] 207 | majesty_longevity = [ 208 | new - old for new, old in zip(claim_block[1:], claim_block[:-1]) 209 | ] 210 | majesty_longevity.append(latest_block - claim_block[-1]) 211 | 212 | for claim, blocks in zip(majesty_claims, majesty_longevity): 213 | claim["longevity"] = blocks 214 | return sorted(majesty_claims, key=itemgetter("longevity"), reverse=True) 215 | 216 | 217 | def braveness(claims: list[dict], majesty_selector: str) -> list[dict]: 218 | assert majesty_selector == "Crown" or majesty_selector == "Sceptre" 219 | 220 | majesty_claims = [] 221 | for claim in claims: 222 | if claim["nft"] == majesty_selector: 223 | majesty_claims += [claim] 224 | claim_donation = [claim["donation"] for claim in majesty_claims] 225 | 226 | majesty_braveness = [1] 227 | for new, old in zip(claim_donation[1:], claim_donation[:-1]): 228 | majesty_braveness.append(math.log(new) - math.log(old)) 229 | 230 | for claim, points in zip(majesty_claims, majesty_braveness): 231 | claim["braveness"] = round(points, 3) 232 | return sorted(majesty_claims, key=itemgetter("braveness"), reverse=True) 233 | --------------------------------------------------------------------------------