├── .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 |
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 | [](https://github.com/AlgoRealm/algorealm/stargazers)
418 |
419 | [](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 |
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 |
--------------------------------------------------------------------------------