Bring DID/NFT effort into release/1.4.0 (#11662)

* initial commit

* assert coin added in test

* add support functions

* add RPC calls (untestested and surely unworking)
also change innerpuz

* missing comma

* update puzzle clvm hex sha256

* fixes

* add load_wallet for nft_wallet

* add singleton 1.1 and update innerpuz for it

* update wallet for new innerpuz

* use singleton 1.1

* swap DID to singleton 1.1

* Fix DID Wallet and DID Innerpuz

* list hint

* don't push automatically

* change to determine_coin_type

* stash debug

* fix get_hints()

* small fix en route to bigger fix later

* push for straya

* checkpoint

* coin discovered in wallet

* re-enable push of attests because message spends are different

* change a few node calls for rebase and rebase

* black and flake8

* flake8 test formatting

* Add transfer test and some transfer functions

* fix bug where innerpuzzle would not recreate itself properly

* fix get_info for received NFT and reovme breakpoint

* fix messages

* debug commit

* receive and send it back!

* send it back and remove NFTs after they're sent

* fix adding duplicates bug

* mint NFT with RPC calls test

* save newer coin info over older info

* finish RPC tests

* update to new version of singleton
black

* change puzzle to remove need for receive spend when trade_price = 0

* Begin conversion to trade price list

* fix test to new trade_price_list system

* Move towards goals in chialisp

* fix clvm testss

* remove unused code

* more chialisp changes working towards CAT royalties

* much progress on CAT royalties

* round down to even on royalties

* add delay to fix inconsistent tests

* remove unused file

* add assertions and fine tune sleep

* add richer support for URIs / metadata and add periphery functions

* switch to metadata, switch to puzzle announcements

* update innerpuz commit hash

* add clvm files to recomp list

* fix rpc test

* flake8 fixes for tests

* flake 8 stupid fix

* Big change to NFT innerpuz (#11141)

* new innerpuz with passing clvm tests

* more work on url updating

* fix broken add_url tests

* fix nft_wallet generate_nft code

* more progress

* fix wallet and change to auto-send on tradeprice == 0

* flake8 and black

* NFTs are always amount 1
removed some unnecessary new_did info detritus from wallet

* move trade_price_list into tp_solution

* Update chia/wallet/nft_wallet/nft_puzzles.py

* Update chia/wallet/nft_wallet/nft_puzzles.py

* Update chia/wallet/nft_wallet/nft_puzzles.py

* Update chia/wallet/nft_wallet/nft_puzzles.py

* Update chia/wallet/nft_wallet/nft_puzzles.py

* Update chia/wallet/nft_wallet/nft_puzzles.py

* Update chia/wallet/nft_wallet/nft_puzzles.py

* Update chia/wallet/nft_wallet/nft_puzzles.py

* Update chia/wallet/nft_wallet/nft_puzzles.py

* Update chia/wallet/nft_wallet/nft_puzzles.py

* typing for create_full_puzzle_with_curry_params

* Typing for update_metadata

* Typing for get_trade_prices_list_from_inner_solution

Co-authored-by: Jeff <jeff@chia.net>

* Merge commit with main (9ff3fc993f) (#11137)

* Merged with main (9ff3fc993f)

* Linter and some test fixes

* isort, linting, test fix

* Fixes and bumped the GUI to point to nftdev

* Couple of fixes

* Bumping GUI to 7aa199a4fb49361e6005c1c9cbdc54f269f9220b

* Bumping GUI to 155866f6d060944b29b40c147f7f02eaab2e50b7

* DID changes all in one  (#10559)

* Chialisp draft

* Modify python code

* Reformat & Fix tests

* Chialisp draft

* Modify python code

* Bug fix & flake8 fix for NFT tests

* Handle recovery

* Chialisp draft

* Modify python code

* Reformat & Fix tests

* Chialisp draft

* Modify python code

* Bug fix & flake8 fix for NFT tests

* add clvm files to recomp list

* fix rpc test

* flake8 fixes for tests

* flake 8 stupid fix

* Bug fix & flake8 fix for NFT tests

* Modify python code

* Chialisp draft

* Modify python code

* Bug fix & flake8 fix for NFT tests

* Generalize the message puzzle

* Add Pubkey as hint

* Receive DID

* Add DID APIs & Tests

* Fix tests

* Test fixes. in_transaction is now passed as a param to the various callers that eventually call save_info.

* Workflow Fix

* Add test config for DID/NFT

* Update workflow test yaml

* Fix install test script

* Fix typo

* Resolve comments

* Fix tests

* Change did_innerpuz and fix wallets for new design (#11196)

* correcting the design of did_innerpuz and related wallet changes

* remove breakpoint comments

* change decimal point accuracy of percentage system

* secure new_amount by fixing it to our current amount

* rename and update comments for new_amount - now my_amount

* rename P2_PUZZLE to simply INNER_PUZZLE

* fix variable re-declaration for flake/merge

* black and flake8 - inclduing wallet_state_manager bug fix

* update RPCs related to add_url
added commented out tests too, but DID needs fixing first

* Fix bugs in transfer case

* Fix pre-commit

* Fix install.sh test for bookworm

Co-authored-by: matt <matt@chia.net>
Co-authored-by: Jeff Cruikshank <jeff@chia.net>
Co-authored-by: matt-o-how <48453825+matt-o-how@users.noreply.github.com>
Co-authored-by: ytx1991 <t.yu@chia.net>

* Merge commit with main 1.3.4 (d154105a6b) (#11276)

* slight simplification of interpreting the bytes received by the timelord. avoid redundant round-trips to strings (#10316)

* move build and twine to be dev deps rather than workflow installs (#10291)

* add db validate function to check consistency of blockchain database (#10398)

* streamable: Cache stream functions (#10419)

Apply the same pattern as we have for deserialization to serialization.

This avoids all those recursive runtime lookups for "how to stream this
object" which brings a nice speedup:

```
compare: benchmark
mode         | µs/iteration old | µs/iteration new | diff %
to_bytes     | 447.57           | 193.56           | -56.75

compare: full_block
mode         | µs/iteration old | µs/iteration new | diff %
to_bytes     | 110.32           | 61.09            | -44.62
```

* Stop assuming `True == 1` (#10396)

* Stop assuming `True == 1`

* Removed unnecessary lines which confuses code readers

* simplify self_hostname. It doesn't need to depend on initial-config.yaml -> create_block_tools() -> global variable bt (#10371)

* Fix balance calculation so it works with future unconfirmed tx (#10447)

* Only update height if we have processed everything before that height. (#10415)

* Only update height if we have processed everything before that height.

* Remove debug return

* Forgot this file

* Better sorting of coin states, and disconnect peer if we are connected to trusted

* Fix disconnect, and don't mutate arguments

* Fix comment

* Update chia/wallet/wallet_node.py

Co-authored-by: Kyle Altendorf <sda@fstab.net>

* Update chia/wallet/wallet_state_manager.py

Co-authored-by: Kyle Altendorf <sda@fstab.net>

* Address comments

Co-authored-by: Kyle Altendorf <sda@fstab.net>

* GC connections in wallet (#10450)

* Fix the year from 2021 to 2022 (#10462)

* Ms.unused code (#10453)

* Remove old backup stuff

* Remove references to CC (couloured coins) which is deprecated terminology

* removed no longer used import

Co-authored-by: Kyle Altendorf <sda@fstab.net>

* Don't load initial data older than 5 days (#10481)

* When loading initial data, don't include IPs older than 5 days in the best timestamp dict

* Don't load version data for hosts older than 5 days

* Update the DMG background image (#10289)

* Update the DMG background image

* Updated with latest design.

* Updated background.tiff with latest design

* Fancier DMG customization support via build_dmg.js

* npm_macos -> npm_macos_m1

* Pass in the app path to build_dmg.js

* Peppering with __init__.py files to satisfy the precommit hook

* Return the fees of an offer via RPC (#10480)

* farmer: Cleanup request retry and some logs (#10484)

* farmer: Bump next update times regardless of the request results

* farmer: Drop additional "success/failure" log logic

We already print the PUT response in `_pool_put_farmer` the other parts 
just lead to confusion if the pool didn't implement the PUT correct.

* farmer: Print error responses from the pool with `WARNING` log level

* Fix method name (#10500)

* updated gui to e2202874e1cb922a57370a47b2aec7bcf152b57d

* reverted to gui 800a0f6556b89e928b1cf027c996b5ed010a7799

* updated gui to 672cf2a74ade67a868df232772bd6358bce8dedf

* Only rewrite config when there is a difference (#10522)

* Only rewrite config when there is a difference

* Use variable

* Preserve existing pool payout_instructions when creating a PoolWallet (#10507)

* Preserve existing pool payout_instructions when creating a PoolWallet

* Updated the logged string when payout_instructions needs to be generated.

* Tests for update_pool_config

* isort

* Logging change, linter fixes, and more comments.

* Abort trusted sync if a state update fails (#10523)

* Abort trusted sync if a state update fails

* Fix lint

* fix updating of sub-epoch-summary map (part of BlockHeightMap) when the entirey change, including genesis changes (#10486)

* Fix propagation of errors when adding a key with an invalid mnemonic (#10274)

* Fix propagation of error messages originating from the keychain server

* Test that adding key with an invalid mnemonic returns the expected error

* Added daemon tests for the add_private_key RPC. Reverted wallet_rpc_client test as the daemon test is better suited for GUI testing.

* Reformatting updates

* Formatting change as requested by the pre-commit gods.

* catch up test_add_private_key() with dynamic ports (#10530)

* Updated background and icon positions. (#10531)

* improve the picking of free ports for tests (#10491)

* Hide balances until we are synced (#10501)

* make multiprocessing start method configurable (#10528)

* make multiprocessing start method configurable

* forkserver

* corrections

* fixup

* optional

* more optional

* stop attempting anchors in the yaml

* rework config handling

* comment

* Update URL for direct download of windows whl for upnp (#10540)

* Add incoming tx records when cancelling an offer (#10538)

* Add incoming tx records when cancelling an offer

* show fee on all txs

* wallet: Reduce log level for `Pulled from queue` message (#10529)

* pools: Drop redundant `PoolWalletInfo.from_json_dict` (#10524)

It exists the same way in its base class `Streamable`.

* correct multiprocessing start method logging, add python_default (#10547)

* correct multiprocessing start method logging, add python_default

* todo -> regular comment

* correct some comments to refer to CATs (#10548)

* correct some comments to refer to CATs

* one more

* and in a test

* Detect hints correctly in the TX (#10543)

* Detect hints correctly in the TX

* Only support list hints

* Allow CAT autodiscovery, refactor CAT default naming (#10308)

* Allow CAT autodiscovery

* Add wallet autodiscovery test

* Use dict get for automatically_add_unknown_cats with default false

* Refactor name generation for new CATs into one place

* Remove hardcoded default cat wallet name from wallet rpc test

* initial-config.yaml comment nit

* Wallet consistancy (#10532)

* use db transaction, -1 in synced up to height, delete unused funcitons

* use transaction info in key-val-store/pool-store

* cat stores

* db lock

* remove unused lock, set synced not always in transaction

* fix store tests

Co-authored-by: wjblanke <wjb98672@gmail.com>

* benchmarks: Implement streamable data comparison (#10433)

* Resubmit peak to timelord for failure. (#10551)

* Initial commit resubmit peak to timelord.

* Change how toandle exception.

* Add some logging. (#10556)

* Fix `install-gui.sh` (#10460)

* Fixed an issue where running  with gui git branch specified failed

* Fixed an issue where install-gui.sh failed if npm>=7 and NodeJS < 16 were installed

* Fixed inconsistent npm path issue

* Fixed typo

* Add fee to changing pool, and more PlotNFT syncing fixes (#10545)

* Add fee to changing pool, and more aggressive disconnect of untrusted

* Don't publish fee TX

* Lint

* Small plotnft related fixes

* More plotnft fixes

* Apply quexington suggestion

* correct param for in_transaction

* Support get_transaction and get_transactions in plotnft

* Re-add publish_transaction and add comment

* Don't rerun additions

* adding 1.3.0 release notes to changelog (#10578)

* adding 1.3.0 release notes to changelog

* Typos. thx @paninaro

* Adding requested adjustments to changelog

* Require pytest-asyncio>=0.17.0 for @fixture() (#10560)

* Convert tests/core/util/test_streamable.py to use pytest. Remove unneeded class. (#10577)

* make sync fixtures not use async def (#10504)

This is particularly relevant in cases where the scope is not function as
that forces use of a wider scoped event loop fixture as well.

* Fixup some hidden test errors (#10442)

* Touching up changelog (#10584)

* removing known issue that was only applicable to a beta release

* Adding additional fixes to the changelog

* Adding Unreleased section to track upcoming changes

* context manager for socket in find_available_listen_port(), catch OSError (#10567)

* Bump CAT wallet test timeout to 40 minutes (#10605)

* Remove block tools and keychain globals (#10358)

* Use bt fixture

* rebase

* Use local_hostname

* Fix test_json (inheritance from unittest.TestCase)

* Use correct BlockTools fixture for test_simulation

* Pass bt fixture into cost calculation tests

* flake8

* Add missing parameters to test functions

* Fix from rebase issues

* Remove set_shared_instance

* Update comment

* Remove unneeded comments

* Remove unused code

* Remove unused code, run `multiprocessing.set_start_method("spawn")` at correct time.

* Revert unrelated change

* Set daemon_port. Teardown services in correct order.
    BIG thanks to Mariano Sorgente for debugging help.

* Add back type signature - rebase issue

* Apply review fixes from Jeff

* Document why we need a later pytest-asyncio version

* Correct type for _configure_legacy_backend

* See what's going on during CI mypy run

* github workflows

* mypy typing

* Remove legacy Keyring create method

* Start daemon first

* Shutdown daemon coroutine properly

* Remove un-needed daemon_port argument

* Set chia-blockchain-gui to hash in main

* Remove connect_to_daemon_port

* Remove code that set "daemon_port" before calling `setup_daemon`

* Remove self_hostname fixture and extra self_hostname global

* Fix two test files that were not importing self_hostname

* self_hostname fixture

* Remove more unused test code

* Simplify  fixture

* a problem with using random.randint() to pick a port is that all processes (running in parallel) are seeded the same, and so pick the same ports at the same time, causing conflicts. This uses proper entropy instead. (#10621)

* `chia keys show` will default to displaying the first observer-derived wallet address. With the addition of the `-d` option, the non-observer derived wallet address can be displayed. (#10615)

* pools: Fix `plotnft claim` command's output (#10609)

If you currently claim rewards `claim_cmd` fails to print the txhash 
with the lookup hint in `submit_tx_with_confirmation`

```
Error performing operation on Plot NFT -f 172057028 wallet id: 12: 
'dict' object has no attribute 'name'
```

Because `submit_tx_with_confirmation` expects a `TransactionRecord` as 
result from its callable parameter `func` but `pw_absorb_rewards` 
returns a dict which includes the `TransactionRecord` as value for the 
key `transaction`. This PR makes sure all other methods used as `func` 
callable have the same return behaviour as `pw_absorb_rewards`. We could 
have adjusted it the other way around (only return `TransactionRecord` 
in `pw_absorb_rewards`) but then we would drop information in the RPC 
client.  


With this PR you get:

```
Do chia wallet get_transaction -f 172057028 -tx 
0x34f74a1ffd9da9a493b78463e635996fd03d4f805ade583acb9764df73355f9c to 
get status
```

* fix initial-config typo - log_maxbytesrotation (#10598)

* Fix invalid DB commit (#10594)

* Fix invalid DB commit

* More fixes

* Add raise

* Fix test

* correct spelling of genrated (#10653)

* fix [Bug] #10569 (#10571)

* Version control (#10479)

* Added version control enforcement to macOS m1

* Added enforced version control

* Added enforce version compliance

* Added enforced version compliance

* Added enforced versioning

* Updating this to include DRY internal action.

* Removed some unintended whitespace.

* Removed some unintended whitespace.

* CI re-run

* Trying to figure out why it's failing one test.

* Trying to figure out why it's failing one test.

* Trying to figure out why it's failing one test.

* plotting: Only lock while actually accessing `PlotManager.plots` (#10675)

* Fix exception when `chia keys migrate` is run without needing migration (#10655)

* Fix exception when `chia keys migrate` is run without needing migration

* Linter fixes

* Slightly different query for V2 DBs for getting compact/uncompact block counts, to ensure the available index is used / avoid a table scan (#10661)

* Check prefix on send_transaction before sending (#10566)

* Check prefix on send_transaction

* Fix failing RPC tests - xch -> txch since tests are a testnet and we enforce valid prefixes with this PR

* Ak.convert fixtures (#10612)

* Use bt fixture

* rebase

* Use local_hostname

* flake8

* Remove set_shared_instance

* Remove unneeded comments

* Revert unrelated change

* Add back type signature - rebase issue

* Correct type for _configure_legacy_backend

* See what's going on during CI mypy run

* github workflows

* mypy typing

* Remove legacy Keyring create method

* Start daemon first

* Set chia-blockchain-gui to hash in main

* Fix two test files that were not importing self_hostname

* self_hostname fixture

* Convert all class fixtures to top level functions

* updated gui to cdfa2b98217fa8755c0da4f7409e6f90032c4c4c

* Better management of KeyringWrapper's keys_root_path when using TempKeyring for tests (#10636)

* Better management of KeyringWrapper's keys_root_path when using TempKeyring for tests.

* Move keys_root_path restoration code into `cleanup()`
Added an assert to detect if an unexpected shared KeyringWrapper is injected during a test.

* Conditionally restore keys_root_path for testing

* better TLS1.3 check (#10552)

* better TLS1.3 check

* catch ValueError instead of Exception

* Code simplification and cleanup

* a few nits in comments

* Add configuration locking (#10680)

* Add configuration locking

Extracted from https://github.com/Chia-Network/chia-blockchain/pull/10631

* note that fasteners will likely be replaced by filelock

* Fix test_multiple_writers on macOS

* create_all_ssl() doesn't need to be inside the config access lock

* add warnings about not using async within get_config_lock() get lock contexts

* no need to pre-touch the lock file

* .yaml.lock instead of just .lock

* test_multiple_writers() is sync

* Revert "add warnings about not using async within get_config_lock() get lock contexts"

This reverts commit 681af3835b.

* reduce lock context size in chia_init()

* use an exit stack in load_config()

* avoid config existence precheck

* only lock around the read in load_config()

* do not raise e, just raise

* tidy new imports

* fix queue empty check in test_config.py

* remove commented out code in test_config.py

* remove unused import

Co-authored-by: Jeff Cruikshank <jeff@chia.net>

* fix usage of the bt fixture in conftest fixtures (#10688)

* bump pre-commit mypy to 0.940 (#10672)

* remove some event_loop() fixtures (#10420)

* remove event_loop() fixtures

* flake8

* flake8

* remove sys.exit() from daemon shutdown

* bump full node test timeout.  a lot...  to see.

* fixup some tests

* back to module scope event loop fixture for test_full_node.py

* Update test_full_node.py

* Iterator...

* for the whole directory

* some fixtures back to module scope for reduced runtime

* back to 40 minute workflow timeouts

* these are being addressed separately

* updated gui to c992d07c956501f92e84ead80127c6b1e882fc21

* tests: Add `_PYTEST_RAISE` to fix exception breakpoints with pytest (#10487)

It's currently not possible to have the debuger stop on an uncaucht 
exception when debugging tests. With this patch, adding 
`_PYTEST_RAISE=1` to the environment variables in the pytest 
configuration template fixes this.

* Fixed test failures on Windows. (#10740)

* Convert helper method do_spend from a class method to a function (#10613)

* Remove unused test code (#10614)

* Ak.setup nodes (#10619)

* Remove unused test code

* Centralize fixture uses of setup_n_nodes

* Centralize fixure uses of setup_two_nodes

* Break up setup_nodes into setup_services, for individial services, and setup_nodes, for initializing different simulator configurations

* Sort imports

* more entropy in random listen ports (#10743)

* update chia-blockchain-gui one commit for npm build fix (#10776)

* Updating Changelog for point release (#10781)

* Updating Changelog for point release

* Adding missing changelog items

* run benchmarks separately (#10754)

* run benchmarks separately

* only run benchmarks once, with the most recent python version we support

* Change name to order of returned values. Enforce mandatory naming and inclusion of start_services parameter (#10769)

* cmds: Implement `chia rpc` command (#10763)

* cmds: Implement `chia rpc` command

* Enable "timelord" client + some refactoring to enable "crawler" client

* wallet: Fix `STANDARD_WALLET` creation for `wallet_info.id != 1` (#10757)

* wallet: Optional wallet type parameter for `get_wallets` and `wallet show` (#10739)

* wallet: Add optional `type` parameter to `get_wallets` and `wallet show`

* tests: Use the `type` parameter for `get_wallets` in pool rpc tests

* cmds: Ask for the name of the wallet type in CLI

* harvester: Reuse legacy refresh interval if new params aren't available (#10729)

* mypy 0.941 for pre-commit (#10728)

* Add maker fee to remaining offer RPCs (#10726)

* Add healthcheck endpoint to rpc services (#10718)

* Add healthcheck endpoint to rpc services

* Trailing whitespace ding

* Fix typos lastest > latest (#10720)

* fix typo in command line argument parsing for chia db validate (#10716)

* New RPC get_coin_records_by_hint - Get coins for a given hint (#10715)

* RPC endpoint to retrieve coins by hint

* RPC client update for get_coin_records_by_hint

* start writing tests for get_coin_records_by_hint

* Address linting concerns.

* Address flake8. Fix the get_coin_ids() call.

* convert hint to bytes32

* tests for get_coin_records_by_hint

Co-authored-by: Amine Khaldi <amine.khaldi@reactos.org>

* require test-cache repo is found in CI (#10711)

* Issues found in RPC API review (#10702)

* Removed unnecessary substitution

* Recovered  property which was accidentally removed in full node RPC API

* Added backward compatibility to `get_additions_and_removals` full_node RPC API

* Fixed full_node rpc client

* minor followup to config locking (#10696)

* minor lock scope reduction

* use the lock in tests

* Use the passed root_path in configure CLI command (#10694)

* Improve the CI runs w.r.t. timelord installation (#10673)

* Superficial analysis showed that only two test groups require (for now) installing the timelord. This change aims to save us hours of CI running time by simply running the install timelord script only for those test groups, with everything else having it omitted. Dedicated to @hoffmang9

* We don't need these anymore.

* less optional around ssl (#10558)

* less optional

* clean up cruft

* more

* more

* just a little less optional

* cmds: Fix trusted peer hint in `chia wallet show` (#10695)

`config` is the root config here.

* Enable clvm_tools_rs by default (#10554)

* Enable clvm_tools_rs by default

* Re-add clvm_tools dep for now as it provides python idioms for interacting with clvm data

* Take lint formatting

* Adam: Try making this non-parallel

* Try fix for threading issue in tests

* Test whether turning off parallel runs causes things to work (temp)

* Test use of temp files in clvm_tools_rs as a candidate solution for atomic replacement of hex output

* Use proper git+https url scheme (oops)

* Update to candidate 0.1.6 so we can test

* Revert version bump to re-test

* Test whether we can re-enable parallelism

* Attempt to mitigate concurrent test running: return own conception of the compiled output.  This will work if the failing path is downstream of recompilation

* fix path to hex file

* Probe for source of 0-length data

* Further exploration: more assertions, hopefully to trigger in the test on ci

* Do an even more paranoid check to verify that we observe a file whose filesystem reported size is much smaller than expected

* Try a heavier handed approach, using heavyweight lockfiles on the filesystem

* Import a simple lockfile implementation and use it to enforce mutual exclusion.  Simplify it and remove the unwanted os traffic

* Take lint, precommit advice, bump to clvm_tools_rs 0.1.6 now that it's released

* Fix lint

* While i was working on this, -n auto was on the command line so i think this didn't actually do anything, but reverting my change just in case

* Lint

* label the hashes re: pr

* Add a lock.py for spot exclusivity using the filesystem (re: adam in the pr) and a convenience wrapper that hides the details

* Formatting warning

* Ensure type info is present and do the obvious return of the inner function's result

* Use double quotes (lint)

* Properly balance blank lines

* Lint: alphabetize imports

* One line is required here (lint)

* Remove unnecessary assignment

* when creating a new blockchain database implicitly, make it v2 (#10498)

* when creating a new blockchain database implicitly, make it v2

* fix config deadlock

* add select_coins RPC method (#10495)

* add select_coins RPC method

* typing fix

* fix typing, casts

* add RPC coin selection tests

* black formatting

* fix select_coins tests

* improve error messages from chia db upgrade, specifically to help users if the disk is full (#10494)

* more set -o errexit (#10468)

* more set -o errexit -o pipefail

* no pipefail, too fancy for dash at least...

* Bump clvm_tools_rs to fix a problem running as daemon caused by old log message that is now eliminated (#10788)

* when running multiple services in the same process (in tests), don't initialize logging for all of them, and don't set the proctitle of the test (#10686)

* stop helping mkdir() do what it already does (#10802)

* stop helping mkdir() do what it already does

* flake8

* Capitalize display of Rpc -> RPC in `chia show -s` (#10797)

* Remove accidental parameters from calls to setup_simulators_and_wallets and prevent future mistakes (#10770)

* stop using deadsnakes, unless we need it (#10752)

* stop using deadsnakes.  and see...

* only install dead snakes stuff if building the timelord on linux

* small change to fix branch in contributing (#10805)

* small change to fix branch in contributing

* Update CONTRIBUTING.md

* Rename confusing fixtures, especially ones with the same name but dif… (#10772)

* Rename confusing fixtures, especially ones with the same name but different implementation

* rename test_environment to test_plot_environment

* Make it so setup_two_nodes is no longer the name of a fixture and a utility function

* revert premature fixture rename: two_wallet_nodes_start_height_1

* atomic rollback for wallet (#10799)

* atomic rollback for wallet

* Handle cases where one node doesn't have the coin we are looking for (#10829)

* Continue if one node doesn't have the coin

* Pass in coin_state list

* Pass in the single coinstate instead of list

* more simplifications

* run tests in parallel in CI (#10499)

* Fix timelord installation for Debian. (#10841)

* add optional persistence to SpendSim (#10780)

* add optional persistence to SpendSim

* Accidental rename

* remove duplicate event_loop (#10768)

* Adding check for python3.9 alongside python3.10 on Arch (#10363)

* Adding check for python3.9 alongside python3.10 on Arch

* Adjusting install.sh instructions for Arch

* Disabling prescribed python install for Arch

* Setting Arch install script to exit 0 to pass tests

* Adding workflow step for functional Arch install testing

* Adding noconfirm to pacman install command

* Relocating Arch support message for install.sh

* use DEFAULT_ROOT_PATH in tests (#10801)

* Disable the pytest-monitor plugin in CI if not checking results (#10837)

* disable the pytest-monitor plugin if not reporting results

pytest-monitor uses multiprocessing and has caused multiple confusing
issues.  Perhaps it can be adjusted to not use multiprocessing, but
for now lets just isolate the oddities to where we actually use it.

* use a template for resource usage check, similar to timelord install

* hint testconfig.custom_vars

* Check for requesting items when creating an offer (#10864)

* updated gui to 054d7b342e7c8284c9b58a775f87d393a1008bfe

* Added `-n`/`--new-address` option to `chia wallet get_address` (#10861)

* Added `-n`/`--new-address` option to `chia wallet get_address`

* Formatting fix

* Complemented --new-address with --latest-address per feedback

* Minor output formatting/enhancements for `chia wallet show` (#10863)

* Minor output formatting/enhancements for `chia wallet show`

* Updated format based on internal poll results

* Linter fix and row rearrangement.

* Hardcoded SSL test certs/keys (#10828)

* Hardcoded SSL test certs/keys

* Added a second set of certs/keys.
Cert/key sets are infinitely cycled-through using get_next_private_ca_cert_and_key() and get_next_nodes_certs_and_keys()

* More cert/key sets and a tool to generate them

* Updated SSL generator to sign with the appropriate root CA.
Fixed linter issues.

* Linter fixes

* Updated generate_ssl_for_nodes() based on feedback

* reduce indentation in a few functions in blockchain.py by negating early-exit checks and loop continues (#10872)

* fix typo and index issues in wallet database (#10273)

* fix typo in wallet_puzzle_store

* check some SQL statements

* deduplicate name SQL index

* deduplicate wallet_type index

* deduplicate wallet_id index

* Update appdmg to 0.6.4 to work with macos 12.3 (#10886)

* fixup and enable condition checking tests (#10888)

* fixup and enable tests for the edge cases of absolute timestamp and absolute height conditions in mempool_manager

* Update chia/full_node/full_node_api.py

Co-authored-by: Adam Kelly <338792+aqk@users.noreply.github.com>

Co-authored-by: Adam Kelly <338792+aqk@users.noreply.github.com>

* Bump colorlog from 5.0.1 to 6.6.0 (#9207)

Bumps [colorlog](https://github.com/borntyping/python-colorlog) from 5.0.1 to 6.6.0.
- [Release notes](https://github.com/borntyping/python-colorlog/releases)
- [Commits](https://github.com/borntyping/python-colorlog/compare/v5.0.1...v6.6.0)

---
updated-dependencies:
- dependency-name: colorlog
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump actions/checkout from 2 to 3 (#10505)

* Bump actions/checkout from 2 to 3

Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update actions in templates too

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gene Hoffman <hoffmang@hoffmang.com>

* Bump github/super-linter from 4.8.1 to 4.9.1 (#10894)

* Bump github/super-linter from 4.8.1 to 4.9.1

Bumps [github/super-linter](https://github.com/github/super-linter) from 4.8.1 to 4.9.1.
- [Release notes](https://github.com/github/super-linter/releases)
- [Changelog](https://github.com/github/super-linter/blob/main/docs/release-process.md)
- [Commits](https://github.com/github/super-linter/compare/v4.8.1...v4.9.1)

---
updated-dependencies:
- dependency-name: github/super-linter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Ignore too-many-function-args in test_type_checking.py

* black

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gene Hoffman <hoffmang@hoffmang.com>

* fix type annotations for get_block_generator() (#10907)

* fix type annotations for FullBlock.header_hash and FullBlock.prev_header_hash (#10909)

* new DBWrapper supporting concurrent readers (#10166)

* new DBWrapper supporting concurrent readers

* adress review comments

* fixup default database version, when file doesn't exist

* remove unused argument

* use rust clvm in Program.run() (#10878)

* remove Program.from_serialized_program

* run the rust clvm implementation (instead of python) even for wallet programs

* Fix flaky trade test (#10921)

* single thread executor (#10919)

* add inline executor and an option to run single-threaded

* add option to run test_full_sync in single-thread mode, to include block validation in profiles. Also attempt to speed it up by disabling db_sync

* await the db commit in the async version of set_db_version (#10906)

* bump pre-commit mypy to v0.942 (#10902)

* bump clvm_tools dependency to make every chia-blockchain installation get the new brun that reports cost accurately (#10880)

* wallet: Drop unused `WalletStateManager.load_wallets` (#10756)

* Switch to integrated lock_and_load_config() context manager (#10698)

* minor lock scope reduction

* use the lock in tests

* Use the passed root_path in configure CLI command

* switch to lock_and_load_config()

* oops

* cleanup

* make _load_config_maybe_locked() private

* black

* Remove future improvement opportunity TODO comment

* move pytest.ini to the root directory (#10892)

* move pytest.ini to the root directory

* pytest.ini: testpaths = tests

https://docs.pytest.org/en/7.1.x/reference/reference.html?highlight=testpaths#confval-testpaths

* set CHIA_ROOT in tests instead of symlinking (#10682)

* attempt to checkout test-cache directly to desired location

* rebuild workflows

* maybe we can use CHIA_ROOT

* use CHIA_ROOT to find blocks and plots for tests

* oops

* more informational printing

* oops

* --capture no for debugging

* flake8

* import os

* undo some unrelated changes now covered elsewhere

* undo some debug changes

* rebuild workflows

* Remove sys.exit() from chia daemon /exit endpoint (#10454)

* asyncio.get_event_loop() is deprecated in 3.10, stop using it (mostly) (#10418)

* asyncio.get_event_loop() is deprecated in 3.10, stop using it

https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop
> Deprecated since version 3.10: Deprecation warning is emitted if there is no running event loop. In future Python releases, this function will be an alias of get_running_loop().

* black

* run tests in CI via coverage (#9704)

* Add coverage (without collection)

* Separate test_block_compression() to avoid coverage-related hangs

* Revert "Separate test_block_compression() to avoid coverage-related hangs"

This reverts commit ebad3d0017.

* multiprocessing.set_start_method("spawn")

* multiprocessing.set_start_method() in conftest.py

* hand hold cc wallet tests

* lint

* spawn for running chia as well

* handle already set start method case

* a bit more timeout for test_multiple_writers

* more timeout for test_writer_lock_blocked_by_readers

* 45 minute tieout for tests/pools/

* 45 minute tieout for tests/pools/

* some more hand holding sleeps

* report coverage in each workflow

only really useful to make sure it is capturing something

* oops

* complete the job name and the JOB_NAME

* better coverage result file names

* reset worker process titles

* rebuild workflows

* rebuild workflows

* black

* black

* rebuild workflows

* push timeouts

* actually include the updated workflows...

* push more workflow timeouts

* parallel=True

* rebuild workflows

* restrict click to < 8.1 for black (#10923)

https://github.com/pallets/click/issues/2225

Doing this instead of updating since updating black will change several
files due to some formatting change.  I would like to take that on
separately from unbreaking CI.

* fixup workflow template merge env duplication (#10925)

* ignore lack of hinting on clvm_tools.binutils.assemble() (#10926)

* Contextualize some store test db names. (#10942)

* Type check values in RL Wallet (#10935)

* Use uint128 for wallet balances (#10936)

* Add more type checks to CAT Wallet (#10934)

* Bump actions/github-script from 4 to 6 (#10246)

* Bump actions/github-script from 4 to 6

Bumps [actions/github-script](https://github.com/actions/github-script) from 4 to 6.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update to github.rest.* for calls to API for compat w/ github-script@v5+

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Chris Marslender <chrismarslender@gmail.com>

* Bump actions/setup-node from 2.4.1 to 3 (#10506)

Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.4.1 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2.4.1...v3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump actions/cache from 2.1.6 to 3 (#10846)

* Bump actions/cache from 2.1.6 to 3

Bumps [actions/cache](https://github.com/actions/cache) from 2.1.6 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Commits](https://github.com/actions/cache/compare/v2.1.6...v3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update actions in templates also

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gene Hoffman <hoffmang@hoffmang.com>

* Fix trailing bytes shown in CAT asset ID row when using `chia wallet show` (#10924)

* Truncate CAT asset_id output to 32 bytes. A wallet RPC change is needed
to properly separate out the asset ID from the TAIL program returned by
`get_all_wallet_info_entries()`

* Move the truncation to the assignment location

* Consolidate test fixtures (#10778)

* Rename confusing fixtures, especially ones with the same name but different implementation

* revert premature fixture rename: two_wallet_nodes_start_height_1

* Consolidate test fixtures

* Quick fix for improper v2 DB initialization when targeting testnet (#10952)

* bump timing threashold for mempool performance test (#10953)

* Ms.parallel pool t (#10966)

* Try parallel pool tests

* Also change workflow files

* Run less combinations

* Todo for bad test

* Try lower n

* run more tests in parallel on CI (#10960)

* run more tests in parallel on CI

* fix test_farmer_get_harvesters to wait for plots to be loaded before asking about them

* improve error message when a block is missing from the blockchain database (#10958)

* improve error message when a block is missing from the blockchain database

* Update chia/full_node/block_height_map.py

Co-authored-by: Kyle Altendorf <sda@fstab.net>

Co-authored-by: Kyle Altendorf <sda@fstab.net>

* Also throw DB error on double spending a coin (#10947)

* Throw error on double spending a coin

* Throw error on double spending a coin

* Improve test

* reorg fixes (#10943)

* when going through a reorg, maintain all chain state until the very end, when the new fork has been fully validated and added

* when rolling back the chain, also rollback the height-to-hash map

* add tests

* Fix the issues in main (failing tests) (#10977)

* Fix one of the issues in test_blockchain

* Only rollback after all async operations are finished

* back to a single option for workflow parallel config (#10979)

* limit test output on CI by dropping -s and -v. Also, only print the 10 slowest tests, instead of all (#10959)

* Ms.flaky gen speed (#10965)

* Flaky test sometimes goes slower than 1 second

* Add sleep to reduce flakiness

* Increase timeout instead of sleeping to hopefully reduce flakiness

* fix test_full_sync.py to only feed the blocks in the main chain to the node (#10974)

* Bump peter-evans/create-pull-request from 3 to 4 (#10950)

Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 3 to 4.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* normalized_to_identity_cc_ip from get_consecutive_blocks was being passed in as overflow_cc_challenge in get_full_block_and_block_record (#10941)

* fix performance tests (#10983)

* Check for vulnerable openssl (#10988)

* Check for vulnerable openssl

* Update OpenSSL on MacOS

* First attempt - openssl Ubuntu 18.04 and 20.04

* place local/bin ahead in PATH

* specify install openssl

* correct path

* run ldconfig

* stop building and check for patched openssl

* spell sudo right by removing it

* Remove openssl building - 1st attempt RHs

* Test Windows OpenSSL version HT @AmineKhaldi

* Non Hobo patch the winstaller for CVE-2022-0778 (#10995)

* apt show not needed (#10997)

* install/upgrade openssl on Arch Linux also (#10999)

* Compile python 3.9.11 which is aware of the openssl issue (#11001)

* install.sh is not upgrading OpenSSL on MacOS (#11003)

* MacOS isn't updating OpenSSL in install.sh

* Exit if no brew on MacOS

* Code the if tree like a pro instead.

Co-authored-by: Kyle Altendorf <sda@fstab.net>

Co-authored-by: Kyle Altendorf <sda@fstab.net>

* force index in get_coin_records_by_names (#10987)

* force index in get_coin_records_by_names

* fix lint

* Fix remaining linting issues (#10962)

* FIx remaining linting issues

* Revert type:ignore

* Revert token_bytes change

* streamable|pools: Fix `Optional` parsing in `dataclass_from_dict` (#10573)

* Test more `Optional` parsing in `dataclass_from_dict`

* Fix optional parsing in `dataclass_from_dict`

* Fix pool wallet / tests

* run_generator2 rust call and compact conditions data structure (#8862)

* use run_generator2 rust call and compact spend bundle conditions data structure pervasively.

* address review comments

* Faster full node tests (#10986)

* Start fast full node tests

* Perf improvement on send_transaction

* Major performance improvement for mempool test

* Speed up another test

* Speed up mempool tests startup

* Lint

* Debug tests

* Try function scope for wallet_nodes

* Update comment

* Force apt to install the things we asked it to (#11047)

* Force apt to install the things we asked it to

* Update .github/workflows/benchmarks.yml

Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com>

Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com>

* github: Drop unused `BUILD_VDF_CLIENT` variables (#11050)

From my understanding this is only used by `chiavdf` source builds which 
happen only if `install-timelord.sh` gets called but it doesn't in the 
addressed cases.

* bump up to 2.1.7 to fix inotify issue resolved by 848 (#11042)

* fix memory leak in test_full_sync (#11004)

* full_node: Drop unused `MempoolManager.constants_json` (#11046)

* simplify some header hash getting and assertions (#11007)

* Remove websockets dependency & do some refactoring (#10611)

* remove old ws

* Prepare test blocks and plots only for tests that need them. This saves us a couple more hours of CI running time. (#10975)

* Adding clean-workspace step to benchmarks (#11063)

* Checkout test blocks and plots for benchmarks workflow (#11068)

* Improve handling of unknown pending balances (likely change from addi… (#10984)

* Improve handling of unknown pending balances (likely change from adding a maker fee).
Minor improvement for fingerprint selection -- enter/return selects the logged-in fingerprint.

* Minor output formatting improvements when showing offer summaries.
Minor wallet key selection improvements.
Added tests for print_offer_summary

* Linter fixes

* isort fix

* Coroutine -> Awaitable

* Removed problematic fee calculation from get_pending_amounts per feedback.

* print average block rate at different block height windows (#11064)

* add -d for Install.ps1 (#11062)

* Set keychain_proxy to None in await_closed() to support reinitialization (#11075)

* Set keychain_proxy to None in await_closed() to support reinitialization.

* Added `shutting_down` param to _await_closed() to control whether the keychain_proxy is closed.

* Significantly speedup preparing test blocks and plots by opting for a release download instead of a shallow git clone, and also by putting a caching layer on top of that. (#11065)

* Bump github/super-linter from 4.9.1 to 4.9.2 (#11067)

Bumps [github/super-linter](https://github.com/github/super-linter) from 4.9.1 to 4.9.2.
- [Release notes](https://github.com/github/super-linter/releases)
- [Changelog](https://github.com/github/super-linter/blob/main/docs/release-process.md)
- [Commits](https://github.com/github/super-linter/compare/v4.9.1...v4.9.2)

---
updated-dependencies:
- dependency-name: github/super-linter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Remove dead snakes usage from benchmark tests (#11053)

* Handle INSTALL_PYTHON_VERSION in Install.ps1, otherwise search 3.9/3.8/3.7 (#11034)

* Handle INSTALL_PYTHON_VERSION in Install.ps1, otherwise search 3.9/3.8/3.7

* fix python availability check in Install.ps1

* when Install.ps1 does not find an acceptable python, list supported versions in order

* Update Install.ps1

Co-authored-by: Matt Hauff <quexington@gmail.com>

Co-authored-by: Matt Hauff <quexington@gmail.com>

* wallet: Drop `puzzles/genesis_checkers.py` and related puzzles (#10790)

Its all duplicated code and puzzles as far as i can tell, see 
`chia/wallet/puzzles/tails.py`.

* Bump cryptography from 3.4.7 to 36.0.2 (#10787)

Bumps [cryptography](https://github.com/pyca/cryptography) from 3.4.7 to 36.0.2.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/3.4.7...36.0.2)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* wallet: Improve logging in `create_more_puzzle_hashes` (#10761)

* wallet: Improve logging in `create_more_puzzle_hashes`

It's pretty spammy currently when scanning puzzle hashes.

* scan -> create, Scanning -> Creating

* `scanning_msg` -> `creating_msg`

* Add /cat_get_unacknowledged API for accessing unknown CATs (#10382)

* Add /cat_get_unacknowledged API for accessing unknown CATs

* Reformat & fix cast issue

* Integration tested & add unit test

* Handle optional uint32

* Reformat

* Reformat

* Reformat

* Merge PR 10308

* Reformat

* Fix concurrent issue

* Add state change notification

* rename API

* Fix failing tests

* Updated state_change name

Co-authored-by: Jeff Cruikshank <jeff@chia.net>

* Increases the probability of connecting to local trusted node (#10633)

* extend tests in test_blockchain to include more conditions, as well as ensuring consensus rules allow unknown condition parameters (#11079)

* chia|tests|github: Implement, integrate and test plot sync protocol (#9695)

* protocols|server: Define new harvester plot refreshing protocol messages

* protocols: Bump `protocol_version` to `0.0.34`

* tests: Introduce `setup_farmer_multi_harvester`

Allows to run a test setup with 1 farmer and mutiple harvesters.

* plotting: Add an initial plot loading indication to `PlotManager`

* plotting|tests: Don't add removed duplicates to `total_result.removed`

`PlotRefreshResult.removed` should only contain plots that were loaded
properly before they were removed. It shouldn't contain e.g. removed
duplicates or invalid plots since those are synced in an extra sync step
and not as diff but as whole list every time.

* harvester: Reset `PlotManager` on shutdown

* plot_sync: Implement plot sync protocol

* farmer|harvester: Integrate and enable plot sync

* tests: Implement tests for the plot sync protocol

* farmer|tests: Drop obsolete harvester caching code

* setup: Add `chia.plot_sync` to packages

* plot_sync: Type hints in `DeltaType`

* plot_sync: Drop parameters in `super()` calls

* plot_sync: Introduce `send_response` helper in `Receiver._process`

* plot_sync: Add some parentheses

Co-authored-by: Kyle Altendorf <sda@fstab.net>

* plot_sync: Additional hint for a `Receiver.process_path_list` parameter

* plot_sync: Force named parameters in `Receiver.process_path_list`

* test: Fix fixtures after rebase

* tests: Fix sorting after rebase

* tests: Return type hint for `plot_sync_setup`

* tests: Rename `WSChiaConnection` and move it in the outer scope

* tests|plot_sync: More type hints

* tests: Rework some delta tests

* tests: Drop a `range` and iterate over the list directly

* tests: Use the proper flags to overwrite

* test: More missing duplicates tests

* tests: Drop `ExpectedResult.reset`

* tests: Reduce some asserts

* tests: Add messages to some `assert False` statements

* tests: Introduce `ErrorSimulation` enum in `test_sync_simulated.py`

* tests: Use `secrects` instead of `Crypto.Random`

* Fixes after rebase

* Import from `typing_extensions` to support python 3.7

* Drop task name to support python 3.7

* Introduce `Sender.syncing`, `Sender.connected` and a log about the task

* Add `tests/plot_sync/config.py`

* Align the multi harvester fixture with what we do in other places

* Update the workflows

Co-authored-by: Kyle Altendorf <sda@fstab.net>

* Add wallentx as additional assignee on mozilla CA update PRs (#11089)

* rebuild workflows (#11092)

* transition to using chia_rs module (#11094)

* Fix the case of claiming a large number of coins (#11038)

* Fix the case of claiming a large number of coins with a fee from a pool wallet

* Revert change to unrelated test

* Set PoolWallet.DEFAULT_MAX_CLAIM_SPENDS to 300

* A few review improvements

* Ms.fast test blockchain (#11051)

* more work on test blockchain

* Optimize test_blockchain.py

* Fix weight proof bug

* Rename variable

* first rc_sub_slot hash bug

* New plots

* try with a new ID

* Run without cache

* Address test blocks and plots preparation.

* Update constant in test_compact_protocol().

* Update this constant too.

* Revert accidental altering of the gui submodule in ae7e3295f280a591e76c4dffdea75fb74ea5de6f.

* Fix benchmark test

* Revert mozilla-ca change

* Rebase on main

Co-authored-by: almog <almogdepaz@gmail.com>
Co-authored-by: Amine Khaldi <amine.khaldi@reactos.org>

* can we get by without dead snakes? (#11070)

* can we get by without dead snakes?

* Update install-timelord.sh

* Revert "Update install-timelord.sh"

This reverts commit cba3250b09.

* do not install python dev package for timelords build in ci

it is already there...

* more quotes for sh

* Changelog from 1.3.3 (#11081)

* Updating changelog

* Update appdmg to 0.6.4 to work with macos 12.3 (#10886)

* restrict click to < 8.1 for black

https://github.com/pallets/click/issues/2225

Doing this instead of updating since updating black will change several
files due to some formatting change.  I would like to take that on
separately from unbreaking CI.

* Check for vulnerable openssl (#10988)

* Check for vulnerable openssl

* Update OpenSSL on MacOS

* First attempt - openssl Ubuntu 18.04 and 20.04

* place local/bin ahead in PATH

* specify install openssl

* correct path

* run ldconfig

* stop building and check for patched openssl

* spell sudo right by removing it

* Remove openssl building - 1st attempt RHs

* Test Windows OpenSSL version HT @AmineKhaldi

* Get updated openssl version (#10991)

* Get updated openssl version

* Update pyinstaller

* Fix typo

* lets try this

* Let's try this

* Try this

Co-authored-by: Earle Lowe <e.lowe@chia.net>

* Gh 1.3.3v2 (#11011)

* Non Hobo patch the winstaller for CVE-2022-0778 (#10995)

* install.sh is not upgrading OpenSSL on MacOS (#11003)

* MacOS isn't updating OpenSSL in install.sh

* Exit if no brew on MacOS

* Code the if tree like a pro instead.

Co-authored-by: Kyle Altendorf <sda@fstab.net>

Co-authored-by: Kyle Altendorf <sda@fstab.net>

* Remove hobo patch

* apt show not needed (#10997)

* install/upgrade openssl on Arch Linux also

* Update CHANGELOG

* revert Arch change backport

Co-authored-by: Kyle Altendorf <sda@fstab.net>

Co-authored-by: wallentx <william.allentx@gmail.com>
Co-authored-by: Chris Marslender <chrismarslender@gmail.com>
Co-authored-by: Gene Hoffman <30377676+hoffmang9@users.noreply.github.com>
Co-authored-by: William Allen <wallentx@users.noreply.github.com>
Co-authored-by: Earle Lowe <e.lowe@chia.net>

* consistently name installer github actions artifact zips (#11096)

* git -C and consistent activation in installer builds (#11098)

* updated gui to d714c21b4ee3ebbc7d18b5f819772cd9868d0bf5

* only check the version once in installer build workflows (#11099)

* updated gui to 5f8b23fc7deb0b07f665c075ed491059f4d8b95c

* updated gui to fccbd3e10d27673e39c01f0f89e47b5455b8331a

* streamable: Simplify and force correct usage (#10509)

* streamable: Merge `strictdataclass` into `Streamable` class

* tests: Test not supported streamable types

* streamable: Reorder decorators

* streamable: Simplify streamable decorator and force correct usage/syntax

* streamable: Just move some stuff around in the file

* streamable: Improve syntax error messages

* mypy: Drop `type_checking.py` and `test_type_checking.py` from exclusion

* streamable: Use cached fields instead of `__annotations__`

This is now possible after merging `__post_init__` into `Streamable`

* Introduce `DefinitionError` as `StreamableError`

* `/t` -> `    `

* Expose farm_block RPC for simulator (#10830)

* expose farm block api to RPC for simulator

* lint

* pre-commit lint

* Ms.plot load perf2 (#10978)

* 2.7 seconds -> 0.45 seconds

* Merge

* Work on create_plots refactor

* Try to fix tests

* Try to fix tests

* Use new functions

* Fix block_tools by adding dir

* Extra argument

* Try to fix cyclic import

* isort

* Drop warning

* Some cleanups around `exclude_final_dir` and directory adding

* Cleanup `min_mainnet_k_size` checks

* Drop unrelated changes

* Fixes after rebase

* Fix cyclic import

* Update tests/block_tools.py

Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com>

* Update tests/block_tools.py

Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com>

Co-authored-by: xdustinface <xdustinfacex@gmail.com>
Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com>

* rebase and more fixes (#10885)

* derivation from just a master public key (#11140)

* Add dependencies macos rhel chiavdf (#11142)

* Added steps, when building the chiavdf wheel for macos and rhel-based systems, to install cmake and/or gmp.

* updated gui to d5b75bcf7a0fe1ef76775e7f1d5e12d169069676

* Revert "derivation from just a master public key (#11140)" (#11143)

This reverts commit db536c615a.

* Mark the github workspace as safe (#11159)

* Mark the github workspace as safe

* Move the git config step after git is installed in the test containers

* updated gui to 81303fb962f4a627a2e1c55098e187a9057745da

* optimize wallet tool by not caching the puzzle_hash -> derivation index, but caching puzzle_hash -> secret key (which is the lookup we're actually interested in). This avoids duplicating the actual derivation (#11154)

* make listen port colissions in CI less likely (#11164)

* Use get latest release endpoint for plotters, so that we ignore any pre-releases that could be returned by listReleases (#11165)

* Build cli only version of debs (#11166)

* Build cli only version of debs

* Export the vars needed by j2

* Fix paths

* Add symlink to chia in /usr/local/bin/

* Upload the cli only debs to s3

* Add init.py

* Ensure SHA is on the dev build for amd64

* add tool to generate a blockchain with full blocks, as a benchmark (#11146)

* Simplify how the chia symlink is created in the CLI .deb (#11188)

* fix block_tools feature when specifying a list of block references. Also add feature keep_going_until_tx_block. (#11185)

* Fix filename of latest intel dev installer (#11203)

* Add start_crawler and start_seeder to pyinstaller config (#11205)

* Pin mac intel installer to 10.15 (#11209)

* Revert "Pin mac intel installer to 10.15 (#11209)" (#11210)

This reverts commit 93a61eece1.

* Adding changelog (#11223)

* Adding changelog (#11223)

Co-authored-by: Arvid Norberg <arvid@libtorrent.org>
Co-authored-by: Kyle Altendorf <sda@fstab.net>
Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com>
Co-authored-by: ChiaMineJP <admin@chiamine.jp>
Co-authored-by: Mariano Sorgente <3069354+mariano54@users.noreply.github.com>
Co-authored-by: David Barratt <davidbarratt@users.noreply.github.com>
Co-authored-by: Chris Marslender <chrismarslender@gmail.com>
Co-authored-by: Matt Hauff <quexington@gmail.com>
Co-authored-by: William Blanke <wjb98672@gmail.com>
Co-authored-by: Juraj Oršulić <juraj.orsulic@fer.hr>
Co-authored-by: Yostra <straya@chia.net>
Co-authored-by: Florin Chirica <fchirica96@gmail.com>
Co-authored-by: Richard Kiss <him@richardkiss.com>
Co-authored-by: William Allen <wallentx@users.noreply.github.com>
Co-authored-by: Adam Kelly <338792+aqk@users.noreply.github.com>
Co-authored-by: Johannes Tysiak <vinyl@users.sf.net>
Co-authored-by: Don Kackman <dkackman@gmail.com>
Co-authored-by: austinsirkin <a.sirkin@chia.net>
Co-authored-by: Earle Lowe <30607889+emlowe@users.noreply.github.com>
Co-authored-by: Brandon Butler <b.butler@chia.net>
Co-authored-by: Dave <72020697+daverof@users.noreply.github.com>
Co-authored-by: Freddie Coleman <f.coleman@hotmail.co.uk>
Co-authored-by: Amine Khaldi <amine.khaldi@reactos.org>
Co-authored-by: arty <art.yerkes@gmail.com>
Co-authored-by: Francesco Truzzi <ftruzzi@users.noreply.github.com>
Co-authored-by: hugepants <hugepants@users.noreply.github.com>
Co-authored-by: Jack Nelson <jack@jacknelson.xyz>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gene Hoffman <hoffmang@hoffmang.com>
Co-authored-by: Gene Hoffman <30377676+hoffmang9@users.noreply.github.com>
Co-authored-by: roseiliend <90035993+roseiliend@users.noreply.github.com>
Co-authored-by: Kronus91 <ytx1991@gmail.com>
Co-authored-by: almog <almogdepaz@gmail.com>
Co-authored-by: wallentx <william.allentx@gmail.com>
Co-authored-by: Earle Lowe <e.lowe@chia.net>
Co-authored-by: xdustinface <xdustinfacex@gmail.com>
Co-authored-by: Patrick Maslana <79757486+pmaslana@users.noreply.github.com>

* WIP commit for the new NFT spec

* Update to optimized singleton

* flesh out ownership layer

* nft_transfer_nft: (#11181)

* Introduce some error handling.
* The new_did_inner_hash and trade_price params should be optional for now.

* Add get_confirmed_balance(), get_unconfirmed_balance(), get_spendable_balance(), get_pending_change_balance() and get_max_send_amount() to NFTWallet. This fixes chia wallet show when NFTs are involved. (#11256)

* Needed for DID CLI: (#11268)

* RPC: Add /did_set_wallet_name and /did_get_wallet_name.
* DIDWallet: Add set_name() and get_name().

* Generalize the Offer class to more than CATs

* Remove CAT dependencies from trade_manager

* Fix offer RPC

* isort

* Further generalize the Offer drivers

* Update trade manager with new generalizations

* Fix offer RPC again

* Move outer_puzzles.py

* pivot from string to clvm for dict entries

* add test coverage for driver dict in RPC

* Remove some CAT specific stuff from tm

* Add comments explaining the changes

* Minor fixes

* isort and flake8

* More linting

* Include drivers in offer summary

* Better autodetection of drivers on offer creation

* Forgot to update rpc test

* checkpoint

* fix test and optimise state_layer puz

* rebuild workflows

* Pass Recovery Info in the Transfer case (#11249)

* Chialisp draft

* Modify python code

* Reformat & Fix tests

* Chialisp draft

* Modify python code

* Bug fix & flake8 fix for NFT tests

* Handle recovery

* Chialisp draft

* Modify python code

* Reformat & Fix tests

* Chialisp draft

* Modify python code

* Bug fix & flake8 fix for NFT tests

* add clvm files to recomp list

* fix rpc test

* flake8 fixes for tests

* flake 8 stupid fix

* Bug fix & flake8 fix for NFT tests

* Modify python code

* Chialisp draft

* Modify python code

* Bug fix & flake8 fix for NFT tests

* Generalize the message puzzle

* Add Pubkey as hint

* Receive DID

* Add DID APIs & Tests

* Fix tests

* Test fixes. in_transaction is now passed as a param to the various callers that eventually call save_info.

* Workflow Fix

* Add test config for DID/NFT

* Update workflow test yaml

* Fix install test script

* Fix typo

* Resolve comments

* Fix tests

* Change did_innerpuz and fix wallets for new design (#11196)

* correcting the design of did_innerpuz and related wallet changes

* remove breakpoint comments

* change decimal point accuracy of percentage system

* secure new_amount by fixing it to our current amount

* rename and update comments for new_amount - now my_amount

* rename P2_PUZZLE to simply INNER_PUZZLE

* fix variable re-declaration for flake/merge

* black and flake8 - inclduing wallet_state_manager bug fix

* update RPCs related to add_url
added commented out tests too, but DID needs fixing first

* Fix bugs in transfer case

* Fix pre-commit

* Fix install.sh test for bookworm

* Pass recovery info in the transfer case

* add clvm files to recomp list

* flake8 fixes for tests

* flake 8 stupid fix

* Modify python code

* Bug fix & flake8 fix for NFT tests

* Pass recovery info in the transfer case

* Fix tests

* Fix security issue of the recovery empty list

* Revert test code

* Allow assign fee when creating the wallet

* reformat

* Add option for pass recovery list

* DID wallet name generation & deduplication

* Fix test

* Remove file reading code

* Fix tests

Co-authored-by: matt <matt@chia.net>
Co-authored-by: Jeff Cruikshank <jeff@chia.net>
Co-authored-by: matt-o-how <48453825+matt-o-how@users.noreply.github.com>
Co-authored-by: ytx1991 <t.yu@chia.net>

* Bumping GUI to 447ac34942facbd1db64755144363268539ecf25

* metadata checkpoint

* fix state layer for metadata updater

* add test for metadata updating

* removed second param for create_ann condition

* forgot to compile clvm

* compiled updated clvm sha256

* * Add fees support to DID wallet creation RPC client. (#11363)

* Initial iteration of the chia wallet did create command. (#11237)

* Add nft_get_nft_info API (#11351)

* Add nft_get_nft_info API

* Refine NFT uncurry code

* Resolve comments

* Resolve comments

Co-authored-by: ytx1991 <t.yu@chia.net>

* almost generates a new nft

* generating nfts

* rpc fix

* DID set wallet name RPC API: Ensure that the wallet type is a DID wallet before proceeding. (#11391)

* Initial iteration of the chia wallet did set_name command. (#11334)

* make NFTWallet a dataclass (#11139)

* make NFTWallet a dataclass

* isort

* flake8

* mypy

* Optional

* Implement nft transfer program

* Resolve comment

* transfer wip

* checkpoint

* basic rpc api working

* Fix data hash issue

* pre commit hooks passing, tests pass

* removed old NFT tests

* workflow files?

* Attempt to fix workflows.

* Workflows?

* Prepare test blocks and plots for NFT wallet tests.

* Reflect the previous commit into workflows.

* clvm compilation fix

* future proof create_coin in nft state layer

* Add RPC client support for the create_new_wallet API. (#11430)

* nft_get_nfts returns a nicer response

* get nfts rpc should now conform to old format

* checkpoint

* Fix tests

* Remove impossible case

* metadata updater working with latest puzzles

* removed unused method

* mypy fix

* Bumping GUI to df86eca99aee33a484570f06588e9fdd6506c661

* added state change events in nft wallet (#11469)

* added state change events in nft wallet

* Attempt to fix workflows.

Co-authored-by: Amine Khaldi <amine.khaldi@reactos.org>

* Bumping GUI to b4d3eebfe33345728a2314031ffad160865019da

* fix for clvm compilation fail

* made nft state layer more future proof

* Add `chia.wallet.nft_wallet` to packages in setup.py

* Initial iteration of the NFT0 chia wallet nft create command. (#11476)

* Add wallet RPC client support for the NFT0 nft_mint_nft API. (#11477)

* Automatically create NFT wallet (#11482)

* Create NFT Wallet if there is not a NFT wallet

* Fix pre-commit

* Remove unnecessary log

* fix to merge into main_dids

* Initial iteration of the NFT0 chia nft mint command.

* Initial iteration of the NFT0 chia wallet nft add_uri command.

* Initial iteration of the NFT0 chia wallet nft transfer command.

* Initial iteration of the NFT0 chia wallet nft list command.

* NFT0: Make the wallet ID required for adding a URI.

* Bumping GUI to 725d77abbae77463fea725396e169f4148b810b6

* NFT0: Make the wallet ID required for transferring an NFT.

* Add fee option for NFT & limit NFT wallet creation (#11492)

* Add fee option for NFT & limit NFT wallet creation

* Fix pre-commit

* Add fee for update metadata

* Add the ability to set fees for minting NFTs, transferring NFTs and adding URIs to NFTs.

* Fix handling fees on the RPC API side.

* fix for missing minted coins

* updating metadata with multiple uris fix

* Consolidate test_did_rpc.py into test_wallet_rpc.py and make the tests use the wallet RPC client. (#4)

* Update workflows and minor formatting.

* faster tests, other fixes

* potentially fixes the did test failure

Co-authored-by: matt <matt@chia.net>
Co-authored-by: Yostra <straya@chia.net>
Co-authored-by: matt-o-how <48453825+matt-o-how@users.noreply.github.com>
Co-authored-by: Jeff <jeff@chia.net>
Co-authored-by: Kronus91 <ytx1991@gmail.com>
Co-authored-by: ytx1991 <t.yu@chia.net>
Co-authored-by: Arvid Norberg <arvid@libtorrent.org>
Co-authored-by: Kyle Altendorf <sda@fstab.net>
Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com>
Co-authored-by: ChiaMineJP <admin@chiamine.jp>
Co-authored-by: Mariano Sorgente <3069354+mariano54@users.noreply.github.com>
Co-authored-by: David Barratt <davidbarratt@users.noreply.github.com>
Co-authored-by: Chris Marslender <chrismarslender@gmail.com>
Co-authored-by: Matt Hauff <quexington@gmail.com>
Co-authored-by: William Blanke <wjb98672@gmail.com>
Co-authored-by: Juraj Oršulić <juraj.orsulic@fer.hr>
Co-authored-by: Florin Chirica <fchirica96@gmail.com>
Co-authored-by: Richard Kiss <him@richardkiss.com>
Co-authored-by: William Allen <wallentx@users.noreply.github.com>
Co-authored-by: Adam Kelly <338792+aqk@users.noreply.github.com>
Co-authored-by: Johannes Tysiak <vinyl@users.sf.net>
Co-authored-by: Don Kackman <dkackman@gmail.com>
Co-authored-by: austinsirkin <a.sirkin@chia.net>
Co-authored-by: Earle Lowe <30607889+emlowe@users.noreply.github.com>
Co-authored-by: Brandon Butler <b.butler@chia.net>
Co-authored-by: Dave <72020697+daverof@users.noreply.github.com>
Co-authored-by: Freddie Coleman <f.coleman@hotmail.co.uk>
Co-authored-by: arty <art.yerkes@gmail.com>
Co-authored-by: Francesco Truzzi <ftruzzi@users.noreply.github.com>
Co-authored-by: hugepants <hugepants@users.noreply.github.com>
Co-authored-by: Jack Nelson <jack@jacknelson.xyz>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gene Hoffman <hoffmang@hoffmang.com>
Co-authored-by: Gene Hoffman <30377676+hoffmang9@users.noreply.github.com>
Co-authored-by: roseiliend <90035993+roseiliend@users.noreply.github.com>
Co-authored-by: almog <almogdepaz@gmail.com>
Co-authored-by: wallentx <william.allentx@gmail.com>
Co-authored-by: Earle Lowe <e.lowe@chia.net>
Co-authored-by: xdustinface <xdustinfacex@gmail.com>
Co-authored-by: Patrick Maslana <79757486+pmaslana@users.noreply.github.com>
Co-authored-by: Sebastjan <trepca@gmail.com>
Co-authored-by: Andreas Greimel <andreas@mintgarden.io>
This commit is contained in:
Amine Khaldi
2022-05-27 13:47:08 -05:00
committed by GitHub
co-authored by matt Yostra matt-o-how Jeff Kronus91 ytx1991 Arvid Norberg Kyle Altendorf dustinface ChiaMineJP Mariano Sorgente David Barratt Chris Marslender Matt Hauff William Blanke Juraj Oršulić Florin Chirica Richard Kiss William Allen Adam Kelly Johannes Tysiak Don Kackman austinsirkin Earle Lowe Brandon Butler Dave Freddie Coleman arty Francesco Truzzi hugepants Jack Nelson dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Gene Hoffman Gene Hoffman roseiliend almog wallentx Earle Lowe xdustinface Patrick Maslana Sebastjan Andreas Greimel
parent 954642a58f
commit d0de8038cd
68 changed files with 4488 additions and 726 deletions
@@ -24,7 +24,7 @@ jobs:
build:
name: MacOS wallet-did_wallet Tests
runs-on: ${{ matrix.os }}
timeout-minutes: 30
timeout-minutes: 50
strategy:
fail-fast: false
max-parallel: 4
@@ -67,7 +67,21 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
# Omitted checking out blocks and plots repo Chia-Network/test-cache
- name: Cache test blocks and plots
uses: actions/cache@v2
id: test-blocks-plots
with:
path: |
${{ github.workspace }}/.chia/blocks
${{ github.workspace }}/.chia/test-plots
key: 0.29.0
- name: Checkout test blocks and plots
if: steps.test-blocks-plots.outputs.cache-hit != 'true'
run: |
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf -
mkdir ${{ github.workspace }}/.chia
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
- name: Run install script
env:
@@ -81,7 +95,7 @@ jobs:
- name: Test wallet-did_wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/did_wallet/test_did.py tests/wallet/did_wallet/test_did_rpc.py
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/did_wallet/test_did.py
- name: Process coverage data
run: |
@@ -0,0 +1,117 @@
#
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
#
name: MacOS wallet-nft_wallet Test
on:
push:
branches:
- 'long_lived/**'
- main
- 'release/**'
tags:
- '**'
pull_request:
branches:
- '**'
concurrency:
# SHA is added to the end if on `main` to let all main workflows run
group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/long_lived/')) && github.sha || '' }}
cancel-in-progress: true
jobs:
build:
name: MacOS wallet-nft_wallet Tests
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
max-parallel: 4
matrix:
python-version: ['3.9', '3.10']
os: [macOS-latest]
env:
CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet
JOB_FILE_NAME: tests_${{ matrix.os }}_python-${{ matrix.python-version }}_wallet-nft_wallet
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Python environment
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Create keychain for CI use
run: |
security create-keychain -p foo chiachain
security default-keychain -s chiachain
security unlock-keychain -p foo chiachain
security set-keychain-settings -t 7200 -u chiachain
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache pip
uses: actions/cache@v3
with:
# Note that new runners may break this https://github.com/actions/cache/issues/292
path: ${{ steps.pip-cache.outputs.dir }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Cache test blocks and plots
uses: actions/cache@v2
id: test-blocks-plots
with:
path: |
${{ github.workspace }}/.chia/blocks
${{ github.workspace }}/.chia/test-plots
key: 0.29.0
- name: Checkout test blocks and plots
if: steps.test-blocks-plots.outputs.cache-hit != 'true'
run: |
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf -
mkdir ${{ github.workspace }}/.chia
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
- name: Run install script
env:
INSTALL_PYTHON_VERSION: ${{ matrix.python-version }}
run: |
brew install boost
sh install.sh -d
# Omitted installing Timelord
- name: Test wallet-nft_wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_wallet.py
- name: Process coverage data
run: |
venv/bin/coverage combine --rcfile=.coveragerc .coverage.*
venv/bin/coverage xml --rcfile=.coveragerc -o coverage.xml
mkdir coverage_reports
cp .coverage "coverage_reports/.coverage.${{ env.JOB_FILE_NAME }}"
cp coverage.xml "coverage_reports/coverage.${{ env.JOB_FILE_NAME }}.xml"
venv/bin/coverage report --rcfile=.coveragerc --show-missing
- name: Publish coverage
uses: actions/upload-artifact@v3
with:
name: coverage
path: coverage_reports/*
if-no-files-found: error
#
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
#
@@ -24,7 +24,7 @@ jobs:
build:
name: Ubuntu wallet-did_wallet Test
runs-on: ${{ matrix.os }}
timeout-minutes: 30
timeout-minutes: 50
strategy:
fail-fast: false
max-parallel: 4
@@ -67,7 +67,21 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
# Omitted checking out blocks and plots repo Chia-Network/test-cache
- name: Cache test blocks and plots
uses: actions/cache@v2
id: test-blocks-plots
with:
path: |
${{ github.workspace }}/.chia/blocks
${{ github.workspace }}/.chia/test-plots
key: 0.29.0
- name: Checkout test blocks and plots
if: steps.test-blocks-plots.outputs.cache-hit != 'true'
run: |
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf -
mkdir ${{ github.workspace }}/.chia
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
- name: Run install script
env:
@@ -80,7 +94,7 @@ jobs:
- name: Test wallet-did_wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/did_wallet/test_did.py tests/wallet/did_wallet/test_did_rpc.py
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/did_wallet/test_did.py
- name: Process coverage data
run: |
@@ -0,0 +1,119 @@
#
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
#
name: Ubuntu wallet-nft_wallet Test
on:
push:
branches:
- 'long_lived/**'
- main
- 'release/**'
tags:
- '**'
pull_request:
branches:
- '**'
concurrency:
# SHA is added to the end if on `main` to let all main workflows run
group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/long_lived/')) && github.sha || '' }}
cancel-in-progress: true
jobs:
build:
name: Ubuntu wallet-nft_wallet Test
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
max-parallel: 4
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10']
os: [ubuntu-latest]
env:
CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet
JOB_FILE_NAME: tests_${{ matrix.os }}_python-${{ matrix.python-version }}_wallet-nft_wallet
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Python environment
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Cache npm
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache pip
uses: actions/cache@v3
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Cache test blocks and plots
uses: actions/cache@v2
id: test-blocks-plots
with:
path: |
${{ github.workspace }}/.chia/blocks
${{ github.workspace }}/.chia/test-plots
key: 0.29.0
- name: Checkout test blocks and plots
if: steps.test-blocks-plots.outputs.cache-hit != 'true'
run: |
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf -
mkdir ${{ github.workspace }}/.chia
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
- name: Run install script
env:
INSTALL_PYTHON_VERSION: ${{ matrix.python-version }}
run: |
sh install.sh -d
# Omitted installing Timelord
- name: Test wallet-nft_wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_wallet.py
- name: Process coverage data
run: |
venv/bin/coverage combine --rcfile=.coveragerc .coverage.*
venv/bin/coverage xml --rcfile=.coveragerc -o coverage.xml
mkdir coverage_reports
cp .coverage "coverage_reports/.coverage.${{ env.JOB_FILE_NAME }}"
cp coverage.xml "coverage_reports/coverage.${{ env.JOB_FILE_NAME }}.xml"
venv/bin/coverage report --rcfile=.coveragerc --show-missing
- name: Publish coverage
uses: actions/upload-artifact@v3
with:
name: coverage
path: coverage_reports/*
if-no-files-found: error
# Omitted resource usage check
#
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
#
@@ -197,6 +197,16 @@ jobs:
apt-get --yes update
apt-get install --yes git lsb-release sudo
# @TODO this step can be removed once Python 3.10 is supported
# Python 3.10 is now the default in bookworm, so install 3.9 specifically so install does not fail
- name: Prepare debian:bookworm
if: ${{ matrix.distribution.name == 'debian:bookworm' }}
env:
DEBIAN_FRONTEND: noninteractive
run: |
apt-get update -y
apt-get install -y python3.9-venv
- name: Prepare Fedora
if: ${{ matrix.distribution.type == 'fedora' }}
run: |
+1
View File
@@ -263,6 +263,7 @@ This release also includes several important performance improvements as a resul
- PlotNFT transactions via CLI (e.g. `chia plotnft join`) now accept a fee parameter, but it is not yet operable.
## 1.2.10 Chia blockchain 2021-10-25
We have some great improvements in this release: We launched our migration of keys to a common encrypted keyring.yaml file, and we secure this with an optional passphrase in both GUI and CLI. We've added a passphrase hint in case you forget your passphrase. More info on our [wiki](https://github.com/Chia-Network/chia-blockchain/wiki/Passphrase-Protected-Chia-Keys-and-Key-Storage-Migration). We also launched a new Chialisp compiler in clvm_tools_rs which substantially improves compile time for Chialisp developers. We also addressed a widely reported issue in which a system failure, such as a power outage, would require some farmers to sync their full node from zero. This release also includes several other improvements and fixes.
+228
View File
@@ -3,6 +3,7 @@ from typing import Any, Dict, Optional, Tuple
import click
from chia.cmds.plotnft import validate_fee
from chia.wallet.util.wallet_types import WalletType
from chia.wallet.transaction_sorting import SortKey
@@ -391,3 +392,230 @@ def cancel_offer_cmd(wallet_rpc_port: Optional[int], fingerprint: int, id: str,
from .wallet_funcs import execute_with_wallet, cancel_offer
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, cancel_offer))
@wallet_cmd.group("did", short_help="DID related actions")
def did_cmd():
pass
@did_cmd.command("create", short_help="Create DID wallet")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-n", "--name", help="Set the DID wallet name", type=str)
@click.option(
"-a",
"--amount",
help="Set the DID amount in mojos. Value must be an odd number.",
type=int,
default=1,
show_default=True,
)
@click.option(
"-m",
"--fee",
help="Set the fees per transaction, in XCH.",
type=str,
default="0",
show_default=True,
callback=validate_fee,
)
def did_create_wallet_cmd(
wallet_rpc_port: Optional[int], fingerprint: int, name: Optional[str], amount: Optional[int], fee: Optional[int]
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, create_did_wallet
extra_params = {"amount": amount, "fee": fee, "name": name}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, create_did_wallet))
@did_cmd.command("set_name", short_help="Set DID wallet name")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-i", "--id", help="Id of the wallet to use", type=int, required=True)
@click.option("-n", "--name", help="Set the DID wallet name", type=str, required=True)
def did_wallet_name_cmd(wallet_rpc_port: Optional[int], fingerprint: int, id: int, name: str) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, did_set_wallet_name
extra_params = {"wallet_id": id, "name": name}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, did_set_wallet_name))
@wallet_cmd.group("nft", short_help="NFT related actions")
def nft_cmd():
pass
@nft_cmd.command("create", short_help="Create an NFT wallet")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
def nft_wallet_create_cmd(wallet_rpc_port: Optional[int], fingerprint: int) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, create_nft_wallet
extra_params: Dict[str, Any] = {}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, create_nft_wallet))
@nft_cmd.command("mint", short_help="Mint an NFT")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-i", "--id", help="Id of the NFT wallet to use", type=int, required=True)
@click.option("-aa", "--artist-address", help="Artist's backpayment address", type=str, required=True)
@click.option("-nh", "--hash", help="NFT content hash", type=str, required=True)
@click.option("-u", "--uris", help="Comma separated list of URIs", type=str, required=True)
@click.option(
"-m",
"--fee",
help="Set the fees per transaction, in XCH.",
type=str,
default="0",
show_default=True,
callback=validate_fee,
)
def nft_mint_cmd(
wallet_rpc_port: Optional[int],
fingerprint: int,
id: int,
artist_address: str,
hash: str,
uris: str,
fee: str,
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, mint_nft
extra_params = {
"wallet_id": id,
"artist_address": artist_address,
"hash": hash,
"uris": [u.strip() for u in uris.split(",")],
"fee": fee,
}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, mint_nft))
@nft_cmd.command("add_uri", short_help="Add an URI to an NFT")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-i", "--id", help="Id of the NFT wallet to use", type=int, required=True)
@click.option("-ni", "--nft-coin-id", help="Id of the NFT coin to add the URI to", type=str, required=True)
@click.option("-u", "--uri", help="URI to add to the NFT", type=str, required=True)
@click.option(
"-m",
"--fee",
help="Set the fees per transaction, in XCH.",
type=str,
default="0",
show_default=True,
callback=validate_fee,
)
def nft_add_uri_cmd(
wallet_rpc_port: Optional[int],
fingerprint: int,
id: int,
nft_coin_id: str,
uri: str,
fee: str,
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, add_uri_to_nft
extra_params = {
"wallet_id": id,
"nft_coin_id": nft_coin_id,
"uri": uri,
"fee": fee,
}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, add_uri_to_nft))
@nft_cmd.command("transfer", short_help="Transfer an NFT")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-i", "--id", help="Id of the NFT wallet to use", type=int, required=True)
@click.option("-ni", "--nft-coin-id", help="Id of the NFT coin to transfer", type=str, required=True)
@click.option("-aa", "--artist-address", help="Target artist's wallet address", type=str, required=True)
@click.option(
"-m",
"--fee",
help="Set the fees per transaction, in XCH.",
type=str,
default="0",
show_default=True,
callback=validate_fee,
)
def nft_transfer_cmd(
wallet_rpc_port: Optional[int],
fingerprint: int,
id: int,
nft_coin_id: str,
artist_address: str,
fee: str,
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, transfer_nft
extra_params = {
"wallet_id": id,
"nft_coin_id": nft_coin_id,
"artist_address": artist_address,
"fee": fee,
}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, transfer_nft))
@nft_cmd.command("list", short_help="List the current NFTs")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-i", "--id", help="Id of the NFT wallet to use", type=int, required=True)
def nft_list_cmd(wallet_rpc_port: Optional[int], fingerprint: int, id: int) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, list_nfts
extra_params = {"wallet_id": id}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, list_nfts))
+100
View File
@@ -643,3 +643,103 @@ async def execute_with_wallet(
print(f"Exception from 'wallet' {e}")
wallet_client.close()
await wallet_client.await_closed()
async def create_did_wallet(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
amount = args["amount"]
fee = args["fee"]
name = args["name"]
try:
response = await wallet_client.create_new_did_wallet(amount, fee, name)
wallet_id = response["wallet_id"]
my_did = response["my_did"]
print(f"Successfully created a DID wallet with name {name} and id {wallet_id} on key {fingerprint}")
print(f"Successfully created a DID {my_did} in the newly created DID wallet")
except Exception as e:
print(f"Failed to create DID wallet: {e}")
async def did_set_wallet_name(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
wallet_id = args["wallet_id"]
name = args["name"]
try:
await wallet_client.did_set_wallet_name(wallet_id, name)
print(f"Successfully set a new name for DID wallet with id {wallet_id}: {name}")
except Exception as e:
print(f"Failed to set DID wallet name: {e}")
async def create_nft_wallet(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
try:
response = await wallet_client.create_new_nft_wallet(None)
wallet_id = response["wallet_id"]
print(f"Successfully created an NFT wallet with id {wallet_id} on key {fingerprint}")
except Exception as e:
print(f"Failed to create NFT wallet: {e}")
async def mint_nft(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
try:
wallet_id = args["wallet_id"]
artist_address = args["artist_address"]
hash = args["hash"]
uris = args["uris"]
fee = args["fee"]
response = await wallet_client.mint_nft(wallet_id, artist_address, hash, uris, fee)
spend_bundle = response["spend_bundle"]
print(f"NFT minted Successfully with spend bundle: {spend_bundle}")
except Exception as e:
print(f"Failed to mint NFT: {e}")
async def add_uri_to_nft(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
try:
wallet_id = args["wallet_id"]
nft_coin_id = args["nft_coin_id"]
uri = args["uri"]
fee = args["fee"]
response = await wallet_client.add_uri_to_nft(wallet_id, nft_coin_id, uri, fee)
spend_bundle = response["spend_bundle"]
print(f"URI added successfully with spend bundle: {spend_bundle}")
except Exception as e:
print(f"Failed to add URI to NFT: {e}")
async def transfer_nft(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
try:
wallet_id = args["wallet_id"]
nft_coin_id = args["nft_coin_id"]
artist_address = args["artist_address"]
fee = args["fee"]
response = await wallet_client.transfer_nft(wallet_id, nft_coin_id, artist_address, fee)
spend_bundle = response["spend_bundle"]
print(f"NFT transferred successfully with spend bundle: {spend_bundle}")
except Exception as e:
print(f"Failed to transfer NFT: {e}")
async def list_nfts(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
wallet_id = args["wallet_id"]
try:
response = await wallet_client.list_nfts(wallet_id)
nft_list = response["nft_list"]
if len(nft_list) > 0:
from chia.wallet.nft_wallet.nft_info import NFTInfo
indent: str = " "
for n in nft_list:
nft = NFTInfo.from_json_dict(n)
print()
print(f"{'Launcher coin ID:'.ljust(23)} {nft.launcher_id}")
print(f"{'Current NFT coin ID:'.ljust(23)} {nft.nft_coin_id}")
print(f"{'NFT content hash:'.ljust(23)} {nft.data_hash}")
print(f"{'Current NFT version:'.ljust(23)} {nft.version}")
print()
print("URIs:")
for uri in nft.data_uris:
print(f"{indent}{uri}")
else:
print(f"No NFTs found for wallet with id {wallet_id} on key {fingerprint}")
except Exception as e:
print(f"Failed to list NFTs for wallet with id {wallet_id} on key {fingerprint}: {e}")
+1
View File
@@ -316,6 +316,7 @@ class MempoolManager:
# build removal list
removal_names: List[bytes32] = [spend.coin_id for spend in npc_result.conds.spends]
if set(removal_names) != set([s.name() for s in new_spend.removals()]):
# If you reach here it's probably because your program reveal doesn't match the coin's puzzle hash
return None, MempoolInclusionStatus.FAILED, Err.INVALID_SPEND_BUNDLE
additions = additions_for_npc(npc_result)
+212 -21
View File
@@ -1,24 +1,27 @@
import asyncio
import dataclasses
import json
import logging
from pathlib import Path
from typing import Callable, Dict, List, Optional, Tuple, Set, Any
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
from blspy import PrivateKey, G1Element
from blspy import G1Element, PrivateKey
from chia.consensus.block_rewards import calculate_base_farmer_reward
from chia.pools.pool_wallet import PoolWallet
from chia.pools.pool_wallet_info import create_pool_state, FARMING_TO_POOL, PoolWalletInfo, PoolState
from chia.pools.pool_wallet_info import FARMING_TO_POOL, PoolState, PoolWalletInfo, create_pool_state
from chia.protocols.protocol_message_types import ProtocolMessageTypes
from chia.server.outbound_message import NodeType, make_msg
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
from chia.types.announcement import Announcement
from chia.types.blockchain_format.coin import Coin, coin_as_list
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.spend_bundle import SpendBundle
from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash
from chia.util.byte_types import hexstr_to_bytes
from chia.util.ints import uint32, uint64, uint8
from chia.util.config import load_config
from chia.util.ints import uint8, uint32, uint64
from chia.util.keychain import KeyringIsLocked, bytes_to_mnemonic, generate_mnemonic
from chia.util.path import path_from_root
from chia.util.ws_message import WsRpcMessage, create_payload_dict
@@ -32,6 +35,8 @@ from chia.wallet.derive_keys import (
match_address_to_sk,
)
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.nft_wallet.nft_puzzles import get_nft_info_from_puzzle
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.puzzle_drivers import PuzzleInfo
from chia.wallet.rl_wallet.rl_wallet import RLWallet
@@ -42,7 +47,6 @@ from chia.wallet.util.transaction_type import TransactionType
from chia.wallet.util.wallet_types import AmountWithPuzzlehash, WalletType
from chia.wallet.wallet_info import WalletInfo
from chia.wallet.wallet_node import WalletNode
from chia.util.config import load_config
# Timeout for response from wallet/full node for sending a transaction
TIMEOUT = 30
@@ -110,14 +114,25 @@ class WalletRpcApi:
"/cancel_offer": self.cancel_offer,
"/get_cat_list": self.get_cat_list,
# DID Wallet
"/did_set_wallet_name": self.did_set_wallet_name,
"/did_get_wallet_name": self.did_get_wallet_name,
"/did_update_recovery_ids": self.did_update_recovery_ids,
"/did_update_metadata": self.did_update_metadata,
"/did_get_pubkey": self.did_get_pubkey,
"/did_get_did": self.did_get_did,
"/did_recovery_spend": self.did_recovery_spend,
"/did_get_recovery_list": self.did_get_recovery_list,
"/did_get_metadata": self.did_get_metadata,
"/did_create_attest": self.did_create_attest,
"/did_get_information_needed_for_recovery": self.did_get_information_needed_for_recovery,
"/did_get_current_coin_info": self.did_get_current_coin_info,
"/did_create_backup_file": self.did_create_backup_file,
"/did_transfer_did": self.did_transfer_did,
# NFT Wallet
"/nft_mint_nft": self.nft_mint_nft,
"/nft_get_nfts": self.nft_get_nfts,
"/nft_transfer_nft": self.nft_transfer_nft,
"/nft_add_uri": self.nft_add_uri,
# RL wallet
"/rl_set_user_info": self.rl_set_user_info,
"/send_clawback_transaction:": self.send_clawback_transaction,
@@ -500,6 +515,11 @@ class WalletRpcApi:
backup_dids.append(hexstr_to_bytes(d))
if len(backup_dids) > 0:
num_needed = uint64(request["num_of_backup_ids_needed"])
metadata: Dict[str, str] = {}
if "metadata" in request:
if type(request["metadata"]) is dict:
metadata = request["metadata"]
async with self.service.wallet_state_manager.lock:
did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_state_manager,
@@ -507,7 +527,11 @@ class WalletRpcApi:
uint64(request["amount"]),
backup_dids,
uint64(num_needed),
metadata,
request.get("wallet_name", None),
uint64(request.get("fee", 0)),
)
my_did = did_wallet.get_my_DID()
return {
"success": True,
@@ -519,7 +543,7 @@ class WalletRpcApi:
elif request["did_type"] == "recovery":
async with self.service.wallet_state_manager.lock:
did_wallet = await DIDWallet.create_new_did_wallet_from_recovery(
wallet_state_manager, main_wallet, request["filename"]
wallet_state_manager, main_wallet, request["backup_data"]
)
assert did_wallet.did_info.temp_coin is not None
assert did_wallet.did_info.temp_puzhash is not None
@@ -543,7 +567,26 @@ class WalletRpcApi:
}
else: # undefined did_type
pass
elif request["wallet_type"] == "nft_wallet":
for wallet in self.service.wallet_state_manager.wallets.values():
if wallet.type() == WalletType.NFT:
# TODO Modify this for NFT1
log.info("NFT wallet already existed, skipping.")
return {
"success": True,
"type": wallet.type(),
"wallet_id": wallet.id(),
}
async with self.service.wallet_state_manager.lock:
nft_wallet: NFTWallet = await NFTWallet.create_new_nft_wallet(
wallet_state_manager,
main_wallet,
)
return {
"success": True,
"type": nft_wallet.type(),
"wallet_id": nft_wallet.id(),
}
elif request["wallet_type"] == "pool_wallet":
if request["mode"] == "new":
owner_puzzle_hash: bytes32 = await self.service.wallet_state_manager.main_wallet.get_puzzle_hash(True)
@@ -1065,10 +1108,28 @@ class WalletRpcApi:
# Distributed Identities
##########################################################################################
async def did_set_wallet_name(self, request):
assert self.service.wallet_state_manager is not None
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
if wallet.type() == WalletType.DISTRIBUTED_ID:
await wallet.set_name(str(request["name"]))
return {"success": True, "wallet_id": wallet_id}
else:
return {"success": False, "error": f"Wallet id {wallet_id} is not a DID wallet"}
async def did_get_wallet_name(self, request):
assert self.service.wallet_state_manager is not None
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
name: str = await wallet.get_name()
return {"success": True, "wallet_id": wallet_id, "name": name}
async def did_update_recovery_ids(self, request):
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
recovery_list = []
success: bool = False
for _ in request["new_list"]:
recovery_list.append(hexstr_to_bytes(_))
if "num_verifications_required" in request:
@@ -1078,9 +1139,27 @@ class WalletRpcApi:
async with self.service.wallet_state_manager.lock:
update_success = await wallet.update_recovery_list(recovery_list, new_amount_verifications_required)
# Update coin with new ID info
spend_bundle = await wallet.create_update_spend()
if update_success:
spend_bundle = await wallet.create_update_spend()
if spend_bundle is not None:
success = True
return {"success": success}
success = spend_bundle is not None and update_success
async def did_update_metadata(self, request):
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
metadata: Dict[str, str] = {}
success: bool = False
if "metadata" in request:
if type(request["metadata"]) is dict:
metadata = request["metadata"]
async with self.service.wallet_state_manager.lock:
update_success = await wallet.update_metadata(metadata)
# Update coin with new ID info
if update_success:
spend_bundle = await wallet.create_update_spend()
if spend_bundle is not None:
success = True
return {"success": success}
async def did_get_did(self, request):
@@ -1105,21 +1184,31 @@ class WalletRpcApi:
return {
"success": True,
"wallet_id": wallet_id,
"recover_list": recover_hex_list,
"recovery_list": recover_hex_list,
"num_required": wallet.did_info.num_of_backup_ids_needed,
}
async def did_get_metadata(self, request):
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
metadata = json.loads(wallet.did_info.metadata)
return {
"success": True,
"wallet_id": wallet_id,
"metadata": metadata,
}
async def did_recovery_spend(self, request):
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
if len(request["attest_filenames"]) < wallet.did_info.num_of_backup_ids_needed:
if len(request["attest_data"]) < wallet.did_info.num_of_backup_ids_needed:
return {"success": False, "reason": "insufficient messages"}
async with self.service.wallet_state_manager.lock:
(
info_list,
message_spend_bundle,
) = await wallet.load_attest_files_for_recovery_spend(request["attest_filenames"])
) = await wallet.load_attest_files_for_recovery_spend(request["attest_data"])
if "pubkey" in request:
pubkey = G1Element.from_bytes(hexstr_to_bytes(request["pubkey"]))
@@ -1155,14 +1244,17 @@ class WalletRpcApi:
info = await wallet.get_info_for_recovery()
coin = hexstr_to_bytes(request["coin_name"])
pubkey = G1Element.from_bytes(hexstr_to_bytes(request["pubkey"]))
spend_bundle = await wallet.create_attestment(
coin, hexstr_to_bytes(request["puzhash"]), pubkey, request["filename"]
spend_bundle, attest_data = await wallet.create_attestment(
coin,
hexstr_to_bytes(request["puzhash"]),
pubkey,
)
if spend_bundle is not None:
if info is not None and spend_bundle is not None:
return {
"success": True,
"message_spend_bundle": bytes(spend_bundle).hex(),
"info": [info[0].hex(), info[1].hex(), info[2]],
"attest_data": attest_data,
}
else:
return {"success": False}
@@ -1182,14 +1274,113 @@ class WalletRpcApi:
"backup_dids": did_wallet.did_info.backup_ids,
}
async def did_get_current_coin_info(self, request):
wallet_id = int(request["wallet_id"])
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
my_did = did_wallet.get_my_DID()
did_coin_threeple = await did_wallet.get_info_for_recovery()
assert my_did is not None
assert did_coin_threeple is not None
return {
"success": True,
"wallet_id": wallet_id,
"my_did": my_did,
"did_parent": did_coin_threeple[0],
"did_innerpuz": did_coin_threeple[1],
"did_amount": did_coin_threeple[2],
}
async def did_create_backup_file(self, request):
wallet_id = int(request["wallet_id"])
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
return {"wallet_id": wallet_id, "success": True, "backup_data": did_wallet.create_backup()}
async def did_transfer_did(self, request):
assert self.service.wallet_state_manager is not None
if await self.service.wallet_state_manager.synced() is False:
raise ValueError("Wallet needs to be fully synced.")
wallet_id = int(request["wallet_id"])
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
puzzle_hash: bytes32 = decode_puzzle_hash(request["inner_address"])
async with self.service.wallet_state_manager.lock:
txs: TransactionRecord = await did_wallet.transfer_did(
puzzle_hash, uint64(request.get("fee", 0)), request.get("with_recovery_info", True)
)
return {
"success": True,
"transaction": txs.to_json_dict_convenience(self.service.config),
"transaction_id": txs.name,
}
##########################################################################################
# NFT Wallet
##########################################################################################
async def nft_mint_nft(self, request) -> Dict:
log.debug("Got minting RPC request: %s", request)
wallet_id = uint32(request["wallet_id"])
assert self.service.wallet_state_manager
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
assert nft_wallet.type() == WalletType.NFT.value, nft_wallet.type()
address = request.get("artist_address")
if isinstance(address, str):
puzzle_hash = decode_puzzle_hash(address)
elif address is None:
puzzle_hash = await nft_wallet.standard_wallet.get_new_puzzlehash()
else:
puzzle_hash = address
metadata = Program.to(
[
("u", request["uris"]),
("h", hexstr_to_bytes(request["hash"])),
]
)
fee = uint64(request.get("fee", 0))
spend_bundle = await nft_wallet.generate_new_nft(metadata, puzzle_hash, fee=fee)
return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle}
async def nft_get_nfts(self, request) -> Dict:
wallet_id = uint32(request["wallet_id"])
assert self.service.wallet_state_manager is not None
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
nfts = nft_wallet.get_current_nfts()
nft_info_list = []
for nft in nfts:
nft_info_list.append(get_nft_info_from_puzzle(nft.full_puzzle, nft.coin))
return {"wallet_id": wallet_id, "success": True, "nft_list": nft_info_list}
async def nft_transfer_nft(self, request):
assert self.service.wallet_state_manager is not None
wallet_id = uint32(request["wallet_id"])
address = request["target_address"]
if isinstance(address, str):
puzzle_hash = decode_puzzle_hash(address)
else:
return dict(success=False, error="target_address parameter missing")
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
try:
wallet_id = int(request["wallet_id"])
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
did_wallet.create_backup(request["filename"])
return {"wallet_id": wallet_id, "success": True}
except Exception:
return {"wallet_id": wallet_id, "success": False}
nft_coin_info = nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(request["nft_coin_id"]))
fee = uint64(request.get("fee", 0))
spend_bundle = await nft_wallet.transfer_nft(nft_coin_info, puzzle_hash, fee=fee)
return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle}
except Exception as e:
log.exception(f"Failed to transfer NFT: {e}")
return {"success": False, "error": str(e)}
async def nft_add_uri(self, request) -> Dict:
assert self.service.wallet_state_manager is not None
wallet_id = uint32(request["wallet_id"])
uri = request["uri"]
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
try:
nft_coin_info = nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(request["nft_coin_id"]))
fee = uint64(request.get("fee", 0))
spend_bundle = await nft_wallet.update_metadata(nft_coin_info, uri, fee=fee)
return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle}
except Exception as e:
log.exception(f"Failed to update NFT metadata: {e}")
return {"success": False, "error": str(e)}
##########################################################################################
# Rate Limited Wallet
+123 -6
View File
@@ -240,18 +240,73 @@ class WalletRpcClient(RpcClient):
return [Coin.from_json_dict(coin) for coin in response["coins"]]
# DID wallet
async def create_new_did_wallet(self, amount):
async def create_new_did_wallet(
self,
amount: int,
fee: int = 0,
name: Optional[str] = "DID Wallet",
backup_ids: List[str] = [],
required_num: int = 0,
) -> Dict:
request: Dict[str, Any] = {
"wallet_type": "did_wallet",
"did_type": "new",
"backup_dids": [],
"num_of_backup_ids_needed": 0,
"backup_dids": backup_ids,
"num_of_backup_ids_needed": required_num,
"amount": amount,
"fee": fee,
"wallet_name": name,
}
response = await self.fetch("create_new_wallet", request)
return response
async def create_new_did_wallet_from_recovery(self, filename):
async def get_did_id(self, wallet_id: int) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
}
response = await self.fetch("did_get_did", request)
return response
async def create_did_backup_file(self, wallet_id: int, filename: str) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"filename": filename,
}
response = await self.fetch("did_create_backup_file", request)
return response
async def update_did_recovery_list(self, wallet_id: int, recovery_list: List[str], num_verification: int) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"new_list": recovery_list,
"num_verifications_required": num_verification,
}
response = await self.fetch("did_update_recovery_ids", request)
return response
async def get_did_recovery_list(self, wallet_id: int) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
}
response = await self.fetch("did_get_recovery_list", request)
return response
async def update_did_metadata(self, wallet_id: int, metadata: Dict) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"metadata": metadata,
}
response = await self.fetch("did_update_metadata", request)
return response
async def get_did_metadata(self, wallet_id: int) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
}
response = await self.fetch("did_get_metadata", request)
return response
async def create_new_did_wallet_from_recovery(self, filename: str) -> Dict:
request: Dict[str, Any] = {
"wallet_type": "did_wallet",
"did_type": "recovery",
@@ -260,7 +315,9 @@ class WalletRpcClient(RpcClient):
response = await self.fetch("create_new_wallet", request)
return response
async def did_create_attest(self, wallet_id, coin_name, pubkey, puzhash, file_name):
async def did_create_attest(
self, wallet_id: int, coin_name: str, pubkey: str, puzhash: str, file_name: str
) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"coin_name": coin_name,
@@ -271,7 +328,7 @@ class WalletRpcClient(RpcClient):
response = await self.fetch("did_create_attest", request)
return response
async def did_recovery_spend(self, wallet_id, attest_filenames):
async def did_recovery_spend(self, wallet_id: int, attest_filenames: str) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"attest_filenames": attest_filenames,
@@ -279,6 +336,26 @@ class WalletRpcClient(RpcClient):
response = await self.fetch("did_recovery_spend", request)
return response
async def did_transfer_did(self, wallet_id: int, address: str, fee: int, with_recovery: bool) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"inner_address": address,
"fee": fee,
"with_recovery_info": with_recovery,
}
response = await self.fetch("did_transfer_did", request)
return response
async def did_set_wallet_name(self, wallet_id: int, name: str) -> Dict:
request = {"wallet_id": wallet_id, "name": name}
response = await self.fetch("did_set_wallet_name", request)
return response
async def did_get_wallet_name(self, wallet_id: int) -> Dict:
request = {"wallet_id": wallet_id}
response = await self.fetch("did_get_wallet_name", request)
return response
# TODO: test all invocations of create_new_pool_wallet with new fee arg.
async def create_new_pool_wallet(
self,
@@ -496,3 +573,43 @@ class WalletRpcClient(RpcClient):
async def cancel_offer(self, trade_id: bytes32, fee=uint64(0), secure: bool = True):
await self.fetch("cancel_offer", {"trade_id": trade_id.hex(), "secure": secure, "fee": fee})
# NFT wallet
async def create_new_nft_wallet(self, did_wallet_id):
request: Dict[str, Any] = {
"wallet_type": "nft_wallet",
"did_wallet_id": did_wallet_id,
}
response = await self.fetch("create_new_wallet", request)
return response
async def mint_nft(self, wallet_id, artist_address, hash, uris, fee):
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"artist_address": artist_address,
"hash": hash,
"uris": uris,
"fee": fee,
}
response = await self.fetch("nft_mint_nft", request)
return response
async def add_uri_to_nft(self, wallet_id, nft_coin_id, uri, fee):
request: Dict[str, Any] = {"wallet_id": wallet_id, "nft_coin_id": nft_coin_id, "uri": uri, "fee": fee}
response = await self.fetch("nft_add_uri", request)
return response
async def transfer_nft(self, wallet_id, nft_coin_id, artist_address, fee):
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"nft_coin_id": nft_coin_id,
"target_address": artist_address,
"fee": fee,
}
response = await self.fetch("nft_transfer_nft", request)
return response
async def list_nfts(self, wallet_id):
request: Dict[str, Any] = {"wallet_id": wallet_id}
response = await self.fetch("nft_get_nfts", request)
return response
-1
View File
@@ -1,6 +1,5 @@
from dataclasses import dataclass
from typing import List
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import SerializedProgram, INFINITE_COST
from chia.util.chain_utils import additions_for_solution, fee_for_solution
+8
View File
@@ -41,6 +41,14 @@ def match_cat_puzzle(puzzle: Program) -> Tuple[bool, Iterator[Program]]:
return False, iter(())
def get_innerpuzzle_from_puzzle(puzzle: Program) -> Program:
mod, curried_args = puzzle.uncurry()
if mod == CAT_MOD:
return curried_args.rest().rest().first()
else:
raise ValueError("Not a CAT puzzle")
def construct_cat_puzzle(mod_code: Program, limitations_program_hash: bytes32, inner_puzzle: Program) -> Program:
"""
Given an inner puzzle hash and tail hash calculate a puzzle program for a specific cc.
-2
View File
@@ -733,7 +733,6 @@ class CATWallet:
max_send = await self.get_max_send_amount()
if payment_sum > max_send:
raise ValueError(f"Can't send more than {max_send} in a single transaction")
unsigned_spend_bundle, chia_tx = await self.generate_unsigned_spendbundle(
payments,
fee,
@@ -742,7 +741,6 @@ class CATWallet:
puzzle_announcements_to_consume=puzzle_announcements_to_consume,
)
spend_bundle = await self.sign(unsigned_spend_bundle)
# TODO add support for array in stored records
tx_list = [
TransactionRecord(
+2 -1
View File
@@ -13,7 +13,7 @@ from chia.types.blockchain_format.coin import Coin
@dataclass(frozen=True)
class DIDInfo(Streamable):
origin_coin: Optional[Coin] # Coin ID of this coin is our DID
backup_ids: List[bytes]
backup_ids: List[bytes32]
num_of_backup_ids_needed: uint64
parent_info: List[Tuple[bytes32, Optional[LineageProof]]] # {coin.name(): LineageProof}
current_inner: Optional[Program] # represents a Program as bytes
@@ -21,3 +21,4 @@ class DIDInfo(Streamable):
temp_puzhash: Optional[bytes32]
temp_pubkey: Optional[bytes]
sent_recovery_transaction: bool
metadata: str # JSON of the user defined metadata
File diff suppressed because it is too large Load Diff
+155 -34
View File
@@ -1,7 +1,7 @@
from clvm_tools import binutils
from clvm_tools.binutils import assemble
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.blockchain_format.program import Program
from typing import List, Optional, Tuple
from typing import List, Optional, Tuple, Iterator, Dict
from blspy import G1Element
from chia.types.blockchain_format.coin import Coin
from chia.types.coin_spend import CoinSpend
@@ -10,50 +10,95 @@ from chia.wallet.puzzles.load_clvm import load_clvm
from chia.types.condition_opcodes import ConditionOpcode
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer.clvm")
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
DID_INNERPUZ_MOD = load_clvm("did_innerpuz.clvm")
SINGLETON_LAUNCHER = load_clvm("singleton_launcher.clvm")
SINGLETON_MOD_HASH = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
LAUNCHER_PUZZLE_HASH = SINGLETON_LAUNCHER.get_tree_hash()
DID_INNERPUZ_MOD_HASH = DID_INNERPUZ_MOD.get_tree_hash()
def create_innerpuz(pubkey: bytes, identities: List[bytes], num_of_backup_ids_needed: uint64) -> Program:
backup_ids_hash = Program(Program.to(identities)).get_tree_hash()
# MOD_HASH MY_PUBKEY RECOVERY_DID_LIST_HASH NUM_VERIFICATIONS_REQUIRED
return DID_INNERPUZ_MOD.curry(pubkey, backup_ids_hash, num_of_backup_ids_needed)
def create_innerpuz(
p2_puzzle: Program,
recovery_list: List[bytes32],
num_of_backup_ids_needed: uint64,
launcher_id: bytes32,
metadata: Program = Program.to([]),
) -> Program:
"""
Create DID inner puzzle
:param p2_puzzle: Standard P2 puzzle
:param recovery_list: A list of DIDs used for the recovery
:param num_of_backup_ids_needed: Need how many DIDs for the recovery
:param launcher_id: ID of the launch coin
:param metadata: DID customized metadata
:return: DID inner puzzle
"""
backup_ids_hash = Program(Program.to(recovery_list)).get_tree_hash()
singleton_struct = Program.to((SINGLETON_MOD_HASH, (launcher_id, LAUNCHER_PUZZLE_HASH)))
return DID_INNERPUZ_MOD.curry(p2_puzzle, backup_ids_hash, num_of_backup_ids_needed, singleton_struct, metadata)
def create_fullpuz(innerpuz: Program, genesis_id: bytes32) -> Program:
def get_inner_puzhash_by_p2(
p2_puzhash: bytes32,
recovery_list: List[bytes32],
num_of_backup_ids_needed: uint64,
launcher_id: bytes32,
metadata: Program = Program.to([]),
) -> bytes32:
"""
Calculate DID inner puzzle hash based on a P2 puzzle hash
:param p2_puzhash: P2 puzzle hash
:param recovery_list: A list of DIDs used for the recovery
:param num_of_backup_ids_needed: Need how many DIDs for the recovery
:param launcher_id: ID of the launch coin
:param metadata: DID customized metadata
:return: DID inner puzzle hash
"""
backup_ids_hash = Program(Program.to(recovery_list)).get_tree_hash()
singleton_struct = Program.to((SINGLETON_MOD_HASH, (launcher_id, LAUNCHER_PUZZLE_HASH)))
return DID_INNERPUZ_MOD.curry(
p2_puzhash, backup_ids_hash, num_of_backup_ids_needed, singleton_struct, metadata
).get_tree_hash(p2_puzhash)
def create_fullpuz(innerpuz: Program, launcher_id: bytes32) -> Program:
"""
Create a full puzzle of DID
:param innerpuz: DID inner puzzle
:param launcher_id:
:return: DID full puzzle
"""
mod_hash = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
# singleton_struct = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
singleton_struct = Program.to((mod_hash, (genesis_id, LAUNCHER_PUZZLE.get_tree_hash())))
singleton_struct = Program.to((mod_hash, (launcher_id, LAUNCHER_PUZZLE.get_tree_hash())))
return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, innerpuz)
def get_pubkey_from_innerpuz(innerpuz: Program) -> G1Element:
ret = uncurry_innerpuz(innerpuz)
if ret is not None:
pubkey_program = ret[0]
else:
raise ValueError("Unable to extract pubkey")
pubkey = G1Element.from_bytes(pubkey_program.as_atom())
return pubkey
def is_did_innerpuz(inner_f: Program):
def is_did_innerpuz(inner_f: Program) -> bool:
"""
You may want to generalize this if different `CAT_MOD` templates are supported.
Check if a puzzle is a DID inner mode
:param inner_f: puzzle
:return: Boolean
"""
return inner_f == DID_INNERPUZ_MOD
def is_did_core(inner_f: Program):
def is_did_core(inner_f: Program) -> bool:
"""
Check if a puzzle is a singleton mod
:param inner_f: puzzle
:return: Boolean
"""
return inner_f == SINGLETON_TOP_LAYER_MOD
def uncurry_innerpuz(puzzle: Program) -> Optional[Tuple[Program, Program]]:
def uncurry_innerpuz(puzzle: Program) -> Optional[Tuple[Program, Program, Program, Program, Program]]:
"""
Take a puzzle and return `None` if it's not a `CAT_MOD` cc, or
a triple of `mod_hash, genesis_coin_checker, inner_puzzle` if it is.
Uncurry a DID inner puzzle
:param puzzle: DID puzzle
:return: Curried parameters
"""
r = puzzle.uncurry()
if r is None:
@@ -62,11 +107,16 @@ def uncurry_innerpuz(puzzle: Program) -> Optional[Tuple[Program, Program]]:
if not is_did_innerpuz(inner_f):
return None
pubkey, id_list, num_of_backup_ids_needed = list(args.as_iter())
return pubkey, id_list
p2_puzzle, id_list, num_of_backup_ids_needed, singleton_struct, metadata = list(args.as_iter())
return p2_puzzle, id_list, num_of_backup_ids_needed, singleton_struct, metadata
def get_innerpuzzle_from_puzzle(puzzle: Program) -> Optional[Program]:
"""
Extract the inner puzzle of a singleton
:param puzzle: Singleton puzzle
:return: Inner puzzle
"""
r = puzzle.uncurry()
if r is None:
return None
@@ -77,13 +127,36 @@ def get_innerpuzzle_from_puzzle(puzzle: Program) -> Optional[Program]:
return INNER_PUZZLE
def create_recovery_message_puzzle(recovering_coin_id: bytes32, newpuz: bytes32, pubkey: G1Element):
puzstring = f"(q . ((0x{ConditionOpcode.CREATE_COIN_ANNOUNCEMENT.hex()} 0x{recovering_coin_id.hex()}) (0x{ConditionOpcode.AGG_SIG_UNSAFE.hex()} 0x{bytes(pubkey).hex()} 0x{newpuz.hex()})))" # noqa
puz = binutils.assemble(puzstring)
return Program.to(puz)
def create_recovery_message_puzzle(recovering_coin_id: bytes32, newpuz: bytes32, pubkey: G1Element) -> Program:
"""
Create attestment message puzzle
:param recovering_coin_id: ID of the DID coin needs to recover
:param newpuz: New wallet puzzle hash
:param pubkey: New wallet pubkey
:return: Message puzzle
"""
return Program.to(
(
1,
[
[ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, recovering_coin_id],
[ConditionOpcode.AGG_SIG_UNSAFE, bytes(pubkey), newpuz],
],
)
)
def create_spend_for_message(parent_of_message, recovering_coin, newpuz, pubkey):
def create_spend_for_message(
parent_of_message: bytes32, recovering_coin: bytes32, newpuz: bytes32, pubkey: G1Element
) -> CoinSpend:
"""
Create a CoinSpend for a atestment
:param parent_of_message: Parent coin ID
:param recovering_coin: ID of the DID coin needs to recover
:param newpuz: New wallet puzzle hash
:param pubkey: New wallet pubkey
:return: CoinSpend
"""
puzzle = create_recovery_message_puzzle(recovering_coin, newpuz, pubkey)
coin = Coin(parent_of_message, puzzle.get_tree_hash(), uint64(0))
solution = Program.to([])
@@ -91,10 +164,58 @@ def create_spend_for_message(parent_of_message, recovering_coin, newpuz, pubkey)
return coinsol
# inspect puzzle and check it is a DID puzzle
def check_is_did_puzzle(puzzle: Program):
def match_did_puzzle(puzzle: Program) -> Tuple[bool, Iterator[Program]]:
"""
Given a puzzle test if it's a DID, if it is, return the curried arguments
:param puzzle: Puzzle
:return: Curried parameters
"""
try:
mod, curried_args = puzzle.uncurry()
if mod == SINGLETON_TOP_LAYER_MOD:
mod, curried_args = curried_args.rest().first().uncurry()
if mod == DID_INNERPUZ_MOD:
return True, curried_args.as_iter()
except Exception:
import traceback
print(f"exception: {traceback.format_exc()}")
return False, iter(())
return False, iter(())
def check_is_did_puzzle(puzzle: Program) -> bool:
"""
Check if a puzzle is a DID puzzle
:param puzzle: Puzzle
:return: Boolean
"""
r = puzzle.uncurry()
if r is None:
return r
inner_f, args = r
return is_did_core(inner_f)
def metadata_to_program(metadata: Dict) -> Program:
"""
Convert the metadata dict to a Chialisp program
:param metadata: User defined metadata
:return: Chialisp program
"""
kv_list = []
for key, value in metadata.items():
kv_list.append((assemble(key), assemble(value)))
return Program.to(kv_list)
def program_to_metadata(program: Program) -> Dict:
"""
Convert a program to a metadata dict
:param program: Chialisp program contains the metadata
:return: Metadata dict
"""
metadata = {}
for key, val in program.as_python():
metadata[str(key, "utf-8")] = str(val, "utf-8")
return metadata
View File
+50
View File
@@ -0,0 +1,50 @@
from dataclasses import dataclass
from typing import List
from chia.util.ints import uint64
from chia.util.streamable import Streamable, streamable
@streamable
@dataclass(frozen=True)
class NFTInfo(Streamable):
"""NFT Info for displaying NFT on the UI"""
launcher_id: str
"""Launcher coin ID"""
nft_coin_id: str
"""Current NFT coin ID"""
did_owner: str
"""Owner DID"""
royalty: uint64
"""Percentage of the transaction fee paid to the author, e.g. 1000 = 1%"""
data_uris: List[str]
""" A list of content URIs"""
data_hash: str
"""Hash of the content"""
metadata_uris: List[str]
"""A list of metadata URIs"""
metadata_hash: str
"""Hash of the metadata"""
license_uris: List[str]
"""A list of license URIs"""
license_hash: str
"""Hash of the license"""
version: str
"""Current NFT version"""
edition_count: uint64
"""How many NFTs in the current series"""
edition_number: uint64
"""Number of the current NFT in the series"""
+101
View File
@@ -0,0 +1,101 @@
import logging
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint64
from chia.wallet.nft_wallet.nft_info import NFTInfo
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
from chia.wallet.puzzles.load_clvm import load_clvm
log = logging.getLogger(__name__)
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm")
LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash()
SINGLETON_MOD_HASH = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
NFT_TRANSFER_PROGRAM = load_clvm("nft_transfer_program.clvm")
OFFER_MOD = load_clvm("settlement_payments.clvm")
NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.clvm")
def create_nft_layer_puzzle_with_curry_params(
metadata: Program, metadata_updater_hash: bytes32, inner_puzzle: Program
) -> Program:
"""Curries params into nft_state_layer.clvm
Args to curry:
NFT_STATE_LAYER_MOD_HASH
METADATA
METADATA_UPDATER_PUZZLE_HASH
INNER_PUZZLE"""
log.debug(
"Creating nft layer puzzle curry: mod_hash: %s, metadata: %r, metadata_hash: %s",
NFT_STATE_LAYER_MOD_HASH,
metadata,
metadata_updater_hash,
)
return NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, metadata_updater_hash, inner_puzzle)
def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Program) -> Program:
log.debug(
"Creating full NFT puzzle with inner puzzle: \n%r\n%r",
singleton_id,
inner_puzzle.get_tree_hash(),
)
singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH)))
full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, inner_puzzle)
log.debug("Created NFT full puzzle with inner: %s", full_puzzle.get_tree_hash())
return full_puzzle
def create_full_puzzle(
singleton_id: bytes32, metadata: Program, metadata_updater_puzhash: bytes32, inner_puzzle: Program
) -> Program:
log.debug(
"Creating full NFT puzzle with: \n%r\n%r\n%r\n%r",
singleton_id,
metadata.get_tree_hash(),
metadata_updater_puzhash,
inner_puzzle.get_tree_hash(),
)
singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH)))
singleton_inner_puzzle = create_nft_layer_puzzle_with_curry_params(metadata, metadata_updater_puzhash, inner_puzzle)
full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, singleton_inner_puzzle)
log.debug("Created NFT full puzzle: %s", full_puzzle.get_tree_hash())
return full_puzzle
def get_nft_info_from_puzzle(puzzle: Program, nft_coin: Coin) -> NFTInfo:
"""
Extract NFT info from a full puzzle
:param puzzle: NFT full puzzle
:param nft_coin: NFT coin
:return: NFTInfo
"""
# TODO Update this method after the NFT code finalized
uncurried_nft: UncurriedNFT = UncurriedNFT.uncurry(puzzle)
data_uris = []
for uri in uncurried_nft.data_uris.as_python():
data_uris.append(str(uri, "utf-8"))
nft_info = NFTInfo(
uncurried_nft.singleton_launcher_id.as_python().hex().upper(),
nft_coin.name().hex().upper(),
uncurried_nft.owner_did.as_python().hex().upper(),
uint64(uncurried_nft.trade_price_percentage.as_int()),
data_uris,
uncurried_nft.data_hash.as_python().hex().upper(),
[],
"",
[],
"",
"NFT0",
uint64(1),
uint64(1),
)
return nft_info
+564
View File
@@ -0,0 +1,564 @@
import json
import logging
import time
from dataclasses import dataclass
from secrets import token_bytes
from typing import Any, Dict, List, Optional, Set, Type, TypeVar
from blspy import AugSchemeMPL, G1Element, G2Element
from clvm.casts import int_from_bytes, int_to_bytes
from chia.clvm.singleton import SINGLETON_TOP_LAYER_MOD
from chia.protocols.wallet_protocol import CoinState
from chia.server.outbound_message import NodeType
from chia.server.ws_connection import WSChiaConnection
from chia.types.announcement import Announcement
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend
from chia.types.spend_bundle import SpendBundle
from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict
from chia.util.ints import uint8, uint32, uint64, uint128
from chia.util.streamable import Streamable, streamable
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.nft_wallet import nft_puzzles
from chia.wallet.nft_wallet.nft_puzzles import LAUNCHER_PUZZLE, NFT_METADATA_UPDATER, NFT_STATE_LAYER_MOD_HASH
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
from chia.wallet.puzzles.load_clvm import load_clvm
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
DEFAULT_HIDDEN_PUZZLE_HASH,
calculate_synthetic_secret_key,
puzzle_for_pk,
solution_for_conditions,
)
from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition
from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.util.compute_memos import compute_memos
from chia.wallet.util.debug_spend_bundle import disassemble
from chia.wallet.util.transaction_type import TransactionType
from chia.wallet.util.wallet_types import WalletType
from chia.wallet.wallet import Wallet
from chia.wallet.wallet_info import WalletInfo
_T_NFTWallet = TypeVar("_T_NFTWallet", bound="NFTWallet")
OFFER_MOD = load_clvm("settlement_payments.clvm")
@streamable
@dataclass(frozen=True)
class NFTCoinInfo(Streamable):
coin: Coin
lineage_proof: LineageProof
full_puzzle: Program
@streamable
@dataclass(frozen=True)
class NFTWalletInfo(Streamable):
my_nft_coins: List[NFTCoinInfo]
did_wallet_id: Optional[uint32] = None
def create_fullpuz(innerpuz: Program, genesis_id: bytes32) -> Program:
mod_hash = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
# singleton_struct = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
singleton_struct = Program.to((mod_hash, (genesis_id, LAUNCHER_PUZZLE.get_tree_hash())))
return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, innerpuz)
class NFTWallet:
wallet_state_manager: Any
log: logging.Logger
wallet_info: WalletInfo
nft_wallet_info: NFTWalletInfo
standard_wallet: Wallet
wallet_id: int
@classmethod
async def create_new_nft_wallet(
cls: Type[_T_NFTWallet],
wallet_state_manager: Any,
wallet: Wallet,
did_wallet_id: uint32 = None,
name: str = "",
in_transaction: bool = False,
) -> _T_NFTWallet:
"""
This must be called under the wallet state manager lock
"""
self = cls()
self.standard_wallet = wallet
self.log = logging.getLogger(name if name else __name__)
self.wallet_state_manager = wallet_state_manager
self.nft_wallet_info = NFTWalletInfo([], did_wallet_id)
info_as_string = json.dumps(self.nft_wallet_info.to_json_dict())
wallet_info = await wallet_state_manager.user_store.create_wallet(
"NFT Wallet" if not name else name,
uint32(WalletType.NFT.value),
info_as_string,
in_transaction=in_transaction,
)
if wallet_info is None:
raise ValueError("Internal Error")
self.wallet_info = wallet_info
self.wallet_id = self.wallet_info.id
self.log.debug("NFT wallet id: %r and standard wallet id: %r", self.wallet_id, self.standard_wallet.wallet_id)
await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id, in_transaction=in_transaction)
self.log.debug("Generated a new NFT wallet: %s", self.__dict__)
if not did_wallet_id:
# default profile wallet
self.log.debug("Standard NFT wallet created")
else:
# TODO: handle DID wallet puzhash
raise NotImplementedError()
return self
@classmethod
async def create(
cls: Type[_T_NFTWallet],
wallet_state_manager: Any,
wallet: Wallet,
wallet_info: WalletInfo,
name: Optional[str] = None,
) -> _T_NFTWallet:
self = cls()
self.log = logging.getLogger(name if name else __name__)
self.wallet_state_manager = wallet_state_manager
self.wallet_info = wallet_info
self.wallet_id = wallet_info.id
self.standard_wallet = wallet
self.wallet_info = wallet_info
self.nft_wallet_info = NFTWalletInfo.from_json_dict(json.loads(wallet_info.data))
return self
@classmethod
def type(cls) -> uint8:
return uint8(WalletType.NFT)
async def get_new_puzzle(self) -> Program:
self.log.debug("Getting new puzzle for NFT wallet: %s", self.id())
return self.puzzle_for_pk((await self.wallet_state_manager.get_unused_derivation_record(self.id())).pubkey)
def id(self) -> uint32:
return self.wallet_info.id
async def get_confirmed_balance(self, record_list=None) -> uint128:
"""The NFT wallet doesn't really have a balance."""
return uint128(0)
async def get_unconfirmed_balance(self, record_list=None) -> uint128:
"""The NFT wallet doesn't really have a balance."""
return uint128(0)
async def get_spendable_balance(self, unspent_records=None) -> uint128:
"""The NFT wallet doesn't really have a balance."""
return uint128(0)
async def get_pending_change_balance(self) -> uint64:
return uint64(0)
async def get_max_send_amount(self, records=None):
"""This is the confirmed balance, which we set to 0 as the NFT wallet doesn't have one."""
return uint128(0)
def get_nft_coin_by_id(self, nft_coin_id: bytes32) -> NFTCoinInfo:
for nft_coin in self.nft_wallet_info.my_nft_coins:
if nft_coin.coin.name() == nft_coin_id:
return nft_coin
raise KeyError(f"Couldn't find coin with id: {nft_coin_id}")
async def add_nft_coin(self, coin: Coin, spent_height: uint32, in_transaction: bool) -> None:
await self.coin_added(coin, spent_height, in_transaction=in_transaction)
async def coin_added(self, coin: Coin, height: uint32, in_transaction: bool) -> None:
"""Notification from wallet state manager that wallet has been received."""
self.log.info(f"NFT wallet %s has been notified that {coin} was added", self.wallet_info.name)
for coin_info in self.nft_wallet_info.my_nft_coins:
if coin_info.coin == coin:
return
wallet_node = self.wallet_state_manager.wallet_node
server = wallet_node.server
full_nodes: Dict[bytes32, WSChiaConnection] = server.connection_by_type.get(NodeType.FULL_NODE, {})
cs: Optional[CoinSpend] = None
coin_states: Optional[List[CoinState]] = await self.wallet_state_manager.wallet_node.get_coin_state(
[coin.parent_coin_info]
)
if not coin_states:
# farm coin
return
assert coin_states
parent_coin = coin_states[0].coin
for node_id in full_nodes:
node = server.all_connections[node_id]
cs = await wallet_node.fetch_puzzle_solution(node, height, parent_coin)
if cs is not None:
break
assert cs is not None
await self.puzzle_solution_received(cs, in_transaction=in_transaction)
async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: bool) -> None:
self.log.debug("Puzzle solution received to wallet: %s", self.wallet_info)
coin_name = coin_spend.coin.name()
puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal))
solution: Program = Program.from_bytes(bytes(coin_spend.solution)).rest().rest().first().first()
# At this point, the puzzle must be a NFT puzzle.
# This method will be called only when the walle state manager uncurried this coin as a NFT puzzle.
uncurried_nft = UncurriedNFT.uncurry(puzzle)
self.log.info(
f"found the info for NFT coin {coin_name} {uncurried_nft.inner_puzzle} {uncurried_nft.singleton_struct}"
)
singleton_id = bytes32(uncurried_nft.singleton_launcher_id.atom)
metadata = uncurried_nft.metadata
new_inner_puzzle = None
parent_inner_puzhash = uncurried_nft.nft_state_layer.get_tree_hash()
self.log.debug("Before spend metadata: %s %s \n%s", metadata, singleton_id, disassemble(solution))
for condition in solution.rest().first().rest().as_iter():
self.log.debug("Checking solution condition: %s", disassemble(condition))
if condition.list_len() < 2:
# invalid condition
continue
condition_code = int_from_bytes(condition.first().atom)
self.log.debug("Checking condition code: %r", condition_code)
if condition_code == -24:
# metadata update
# (-24 (meta updater puzzle) url)
metadata_list = list(metadata.as_python())
new_metadata = []
for metadata_entry in metadata_list:
key = metadata_entry[0]
if key == b"u":
new_metadata.append((b"u", [condition.rest().rest().first().atom] + list(metadata_entry[1:])))
else:
new_metadata.append((b"h", metadata_entry[1]))
metadata = Program.to(new_metadata)
elif condition_code == 51 and int_from_bytes(condition.rest().rest().first().atom) == 1:
puzhash = bytes32(condition.rest().first().atom)
self.log.debug("Got back puzhash from solution: %s", puzhash)
derivation_record: Optional[
DerivationRecord
] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(puzhash)
if derivation_record is None:
# we potentially sent it somewhere
await self.remove_coin(coin_spend.coin, in_transaction=in_transaction)
return
new_inner_puzzle = puzzle_for_pk(derivation_record.pubkey)
else:
raise ValueError("Invalid condition")
if new_inner_puzzle is None:
raise ValueError("Invalid puzzle")
parent_coin = None
coin_record = await self.wallet_state_manager.coin_store.get_coin_record(coin_name)
if coin_record is None:
coin_states: Optional[List[CoinState]] = await self.wallet_state_manager.wallet_node.get_coin_state(
[coin_name]
)
if coin_states is not None:
parent_coin = coin_states[0].coin
if coin_record is not None:
parent_coin = coin_record.coin
if parent_coin is None:
raise ValueError("Error in finding parent")
self.log.debug("Got back updated metadata: %s", metadata)
child_puzzle: Program = nft_puzzles.create_full_puzzle(
singleton_id,
metadata,
bytes32(uncurried_nft.metdata_updater_hash.atom),
new_inner_puzzle,
)
self.log.debug(
"Created NFT full puzzle with inner: %s",
nft_puzzles.create_full_puzzle_with_nft_puzzle(singleton_id, uncurried_nft.inner_puzzle),
)
child_coin: Optional[Coin] = None
for new_coin in coin_spend.additions():
self.log.debug(
"Comparing addition: %s with %s, amount: %s ",
new_coin.puzzle_hash,
child_puzzle.get_tree_hash(),
new_coin.amount,
)
if new_coin.puzzle_hash == child_puzzle.get_tree_hash():
child_coin = new_coin
break
else:
raise ValueError("Invalid NFT spend on %r" % coin_name)
self.log.info("Adding a new NFT to wallet: %s", child_coin)
await self.add_coin(
child_coin,
child_puzzle,
LineageProof(parent_coin.parent_coin_info, parent_inner_puzhash, parent_coin.amount),
in_transaction=in_transaction,
)
async def add_coin(self, coin: Coin, puzzle: Program, lineage_proof: LineageProof, in_transaction: bool) -> None:
my_nft_coins = self.nft_wallet_info.my_nft_coins
for coin_info in my_nft_coins:
if coin_info.coin == coin:
my_nft_coins.remove(coin_info)
my_nft_coins.append(NFTCoinInfo(coin, lineage_proof, puzzle))
new_nft_wallet_info = NFTWalletInfo(
my_nft_coins,
self.nft_wallet_info.did_wallet_id,
)
await self.save_info(new_nft_wallet_info, in_transaction=in_transaction)
await self.wallet_state_manager.add_interested_coin_ids([coin.name()], in_transaction=in_transaction)
self.wallet_state_manager.state_changed("nft_coin_added", self.wallet_info.id)
return
async def remove_coin(self, coin: Coin, in_transaction: bool) -> None:
my_nft_coins = self.nft_wallet_info.my_nft_coins
for coin_info in my_nft_coins:
if coin_info.coin == coin:
my_nft_coins.remove(coin_info)
new_nft_wallet_info = NFTWalletInfo(
my_nft_coins,
self.nft_wallet_info.did_wallet_id,
)
await self.save_info(new_nft_wallet_info, in_transaction=in_transaction)
self.wallet_state_manager.state_changed("nft_coin_removed", self.wallet_info.id)
return
def puzzle_for_pk(self, pk: G1Element) -> Program:
if not self.nft_wallet_info.did_wallet_id:
inner_puzzle = self.standard_wallet.puzzle_for_pk(bytes(pk))
else:
raise NotImplementedError
provenance_puzzle = Program.to([NFT_STATE_LAYER_MOD_HASH, inner_puzzle])
self.log.debug(
"Wallet name %s generated a puzzle: %s", self.wallet_info.name, provenance_puzzle.get_tree_hash()
)
return provenance_puzzle
async def generate_new_nft(
self, metadata: Program, target_puzzle_hash: bytes32 = None, fee: uint64 = uint64(0)
) -> Optional[SpendBundle]:
"""
This must be called under the wallet state manager lock
"""
amount = uint64(1)
coins = await self.standard_wallet.select_coins(amount)
if coins is None:
return None
self.log.debug("Attempt to generate a new NFT")
origin = coins.copy().pop()
genesis_launcher_puz = nft_puzzles.LAUNCHER_PUZZLE
launcher_coin = Coin(origin.name(), genesis_launcher_puz.get_tree_hash(), uint64(amount))
self.log.debug("Generating NFT with launcher coin %s and metadata: %s", launcher_coin, metadata)
inner_puzzle = await self.standard_wallet.get_new_puzzle()
# singleton eve
eve_fullpuz = nft_puzzles.create_full_puzzle(
launcher_coin.name(), metadata, NFT_METADATA_UPDATER.get_tree_hash(), inner_puzzle
)
# launcher announcement
announcement_set: Set[Announcement] = set()
announcement_message = Program.to([eve_fullpuz.get_tree_hash(), amount, []]).get_tree_hash()
announcement_set.add(Announcement(launcher_coin.name(), announcement_message))
self.log.debug(
"Creating transaction for launcher: %s and other coins: %s (%s)", origin, coins, announcement_set
)
# store the launcher transaction in the wallet state
tx_record: Optional[TransactionRecord] = await self.standard_wallet.generate_signed_transaction(
uint64(amount),
genesis_launcher_puz.get_tree_hash(),
fee,
origin.name(),
coins,
None,
False,
announcement_set,
)
genesis_launcher_solution = Program.to([eve_fullpuz.get_tree_hash(), amount, []])
# launcher spend to generate the singleton
launcher_cs = CoinSpend(launcher_coin, genesis_launcher_puz, genesis_launcher_solution)
launcher_sb = SpendBundle([launcher_cs], AugSchemeMPL.aggregate([]))
eve_coin = Coin(launcher_coin.name(), eve_fullpuz.get_tree_hash(), uint64(amount))
if tx_record is None or tx_record.spend_bundle is None:
return None
if not target_puzzle_hash:
target_puzzle_hash = inner_puzzle.get_tree_hash()
condition_list = [make_create_coin_condition(target_puzzle_hash, amount, [target_puzzle_hash])]
innersol = solution_for_conditions(condition_list)
# EVE SPEND BELOW
fullsol = Program.to(
[
[launcher_coin.parent_coin_info, launcher_coin.amount],
eve_coin.amount,
Program.to(
[
innersol,
amount,
0,
]
),
]
)
list_of_coinspends = [CoinSpend(eve_coin, eve_fullpuz, fullsol)]
eve_spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([]))
eve_spend_bundle = await self.sign(eve_spend_bundle)
full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend_bundle, launcher_sb])
nft_record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
to_puzzle_hash=eve_fullpuz.get_tree_hash(),
amount=uint64(amount),
fee_amount=fee,
confirmed=False,
sent=uint32(0),
spend_bundle=full_spend,
additions=full_spend.additions(),
removals=full_spend.removals(),
wallet_id=self.wallet_info.id,
sent_to=[],
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32(token_bytes()),
memos=list(compute_memos(full_spend).items()),
)
await self.standard_wallet.push_transaction(nft_record)
return nft_record.spend_bundle
async def sign(self, spend_bundle: SpendBundle) -> SpendBundle:
sigs: List[G2Element] = []
for spend in spend_bundle.coin_spends:
try:
uncurried_nft = UncurriedNFT.uncurry(spend.puzzle_reveal.to_program())
except Exception as e:
# This only happens while you changed the NFT chialisp but didn't update the UncurriedNFT class.
self.log.error(e)
else:
self.log.debug("Found a NFT state layer to sign")
puzzle_hash = uncurried_nft.inner_puzzle.get_tree_hash()
keys = await self.wallet_state_manager.get_keys(puzzle_hash)
assert keys
_, private = keys
synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH)
error, conditions, cost = conditions_dict_for_solution(
spend.puzzle_reveal.to_program(),
spend.solution.to_program(),
self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM,
)
if conditions is not None:
synthetic_pk = synthetic_secret_key.get_g1()
for pk, msg in pkm_pairs_for_conditions_dict(
conditions, spend.coin.name(), self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
):
try:
assert bytes(synthetic_pk) == pk
sigs.append(AugSchemeMPL.sign(synthetic_secret_key, msg))
except AssertionError:
raise ValueError("This spend bundle cannot be signed by the NFT wallet")
agg_sig = AugSchemeMPL.aggregate(sigs)
return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)])
async def _make_nft_transaction(
self, nft_coin_info: NFTCoinInfo, inner_solution: Program, fee: uint64 = uint64(0)
) -> TransactionRecord:
coin = nft_coin_info.coin
amount = coin.amount
full_puzzle = nft_coin_info.full_puzzle
lineage_proof = nft_coin_info.lineage_proof
self.log.debug("Inner solution: %r", disassemble(inner_solution))
full_solution = Program.to(
[
[lineage_proof.parent_name, lineage_proof.inner_puzzle_hash, lineage_proof.amount],
coin.amount,
Program.to(
[
inner_solution,
amount,
0,
]
),
]
)
list_of_coinspends = [CoinSpend(coin, full_puzzle.to_serialized_program(), full_solution)]
spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([]))
spend_bundle = await self.sign(spend_bundle)
full_spend = SpendBundle.aggregate([spend_bundle])
self.log.debug("Memos are: %r", list(compute_memos(full_spend).items()))
nft_record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
to_puzzle_hash=full_puzzle.get_tree_hash(),
amount=uint64(amount),
fee_amount=fee,
confirmed=False,
sent=uint32(0),
spend_bundle=full_spend,
additions=full_spend.additions(),
removals=full_spend.removals(),
wallet_id=self.wallet_info.id,
sent_to=[],
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32(token_bytes()),
memos=list(compute_memos(full_spend).items()),
)
return nft_record
async def update_metadata(
self, nft_coin_info: NFTCoinInfo, uri: str, fee: uint64 = uint64(0)
) -> Optional[SpendBundle]:
coin = nft_coin_info.coin
# we're not changing it
uncurried_nft = UncurriedNFT.uncurry(nft_coin_info.full_puzzle)
puzzle_hash = uncurried_nft.inner_puzzle.get_tree_hash()
condition_list = [make_create_coin_condition(puzzle_hash, coin.amount, [puzzle_hash])]
condition_list.append([int_to_bytes(-24), NFT_METADATA_UPDATER, uri.encode("utf-8")])
self.log.info("Attempting to add a url to NFT coin %s in the metadata: %s", nft_coin_info, uri)
inner_solution = solution_for_conditions(condition_list)
nft_tx_record = await self._make_nft_transaction(nft_coin_info, inner_solution, fee)
await self.standard_wallet.push_transaction(nft_tx_record)
return nft_tx_record.spend_bundle
async def transfer_nft(
self,
nft_coin_info: NFTCoinInfo,
puzzle_hash: bytes32,
did_hash=None,
fee: uint64 = uint64(0),
) -> Optional[SpendBundle]:
self.log.debug("Attempt to transfer a new NFT")
coin = nft_coin_info.coin
self.log.debug("Transfering NFT coin %r to puzhash: %s", nft_coin_info, puzzle_hash)
amount = coin.amount
condition_list = [make_create_coin_condition(puzzle_hash, amount, [bytes32(puzzle_hash)])]
self.log.debug("Condition for new coin: %r", condition_list)
inner_solution = solution_for_conditions(condition_list)
nft_tx_record = await self._make_nft_transaction(nft_coin_info, inner_solution, fee)
await self.standard_wallet.push_transaction(nft_tx_record)
return nft_tx_record.spend_bundle
def get_current_nfts(self) -> List[NFTCoinInfo]:
return self.nft_wallet_info.my_nft_coins
async def save_info(self, nft_info: NFTWalletInfo, in_transaction: bool) -> None:
self.nft_wallet_info = nft_info
current_info = self.wallet_info
data_str = json.dumps(nft_info.to_json_dict())
wallet_info = WalletInfo(current_info.id, current_info.name, current_info.type, data_str)
self.wallet_info = wallet_info
await self.wallet_state_manager.user_store.update_wallet(wallet_info, in_transaction)
+122
View File
@@ -0,0 +1,122 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Type, TypeVar
from chia.types.blockchain_format.program import Program
from chia.wallet.puzzles.load_clvm import load_clvm
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
NFT_MOD = load_clvm("nft_state_layer.clvm")
_T_UncurriedNFT = TypeVar("_T_UncurriedNFT", bound="UncurriedNFT")
@dataclass(frozen=True)
class UncurriedNFT:
"""
A simple solution for uncurry NFT puzzle.
Initial the class with a full NFT puzzle, it will do a deep uncurry.
This is the only place you need to change after modified the Chialisp curried parameters.
"""
nft_mod_hash: Program
"""NFT module hash"""
nft_state_layer: Program
"""NFT state layer puzzle"""
singleton_struct: Program
"""
Singleton struct
[singleton_mod_hash, singleton_launcher_id, launcher_puzhash]
"""
singleton_mod_hash: Program
singleton_launcher_id: Program
launcher_puzhash: Program
owner_did: Program
"""Owner's DID"""
metdata_updater_hash: Program
"""Metadata updater puzzle hash"""
transfer_program_hash: Program
"""Puzzle hash of the transfer program"""
transfer_program_curry_params: Program
"""
Curried parameters of the transfer program
[royalty_address, trade_price_percentage, settlement_mod_hash, cat_mod_hash]
"""
royalty_address: Program
trade_price_percentage: Program
settlement_mod_hash: Program
cat_mod_hash: Program
metadata: Program
"""
NFT metadata
[("u", data_uris), ("h", data_hash)]
"""
data_uris: Program
data_hash: Program
inner_puzzle: Program
"""NFT state layer inner puzzle"""
@classmethod
def uncurry(cls: Type[_T_UncurriedNFT], puzzle: Program) -> UncurriedNFT:
"""
Try to uncurry a NFT puzzle
:param cls UncurriedNFT class
:param puzzle: Puzzle program
:return Uncurried NFT
"""
mod, curried_args = puzzle.uncurry()
if mod != SINGLETON_TOP_LAYER_MOD:
raise ValueError(f"Cannot uncurry NFT puzzle, failed on singleton top layer: Mod {mod}")
try:
(singleton_struct, nft_state_layer) = curried_args.as_iter()
singleton_mod_hash = singleton_struct.first()
singleton_launcher_id = singleton_struct.rest().first()
launcher_puzhash = singleton_struct.rest().rest()
except ValueError as e:
raise ValueError(f"Cannot uncurry singleton top layer: Args {curried_args}") from e
mod, curried_args = curried_args.rest().first().uncurry()
if mod != NFT_MOD:
raise ValueError(f"Cannot uncurry NFT puzzle, failed on NFT state layer: Mod {mod}")
try:
# Set nft parameters
(nft_mod_hash, metadata, metdata_updater_hash, inner_puzzle) = curried_args.as_iter()
# Set metadata
for kv_pair in metadata.as_iter():
if kv_pair.first().as_atom() == b"u":
data_uris = kv_pair.rest()
if kv_pair.first().as_atom() == b"h":
data_hash = kv_pair.rest()
except Exception as e:
raise ValueError(f"Cannot uncurry NFT state layer: Args {curried_args}") from e
return cls(
nft_mod_hash=nft_mod_hash,
nft_state_layer=nft_state_layer,
singleton_struct=singleton_struct,
singleton_mod_hash=singleton_mod_hash,
singleton_launcher_id=singleton_launcher_id,
launcher_puzhash=launcher_puzhash,
metadata=metadata,
data_uris=data_uris,
data_hash=data_hash,
metdata_updater_hash=metdata_updater_hash,
inner_puzzle=inner_puzzle,
# TODO Set/Remove following fields after NFT1 implemented
owner_did=Program.to([]),
transfer_program_hash=Program.to([]),
transfer_program_curry_params=Program.to([]),
royalty_address=Program.to([]),
trade_price_percentage=Program.to([]),
settlement_mod_hash=Program.to([]),
cat_mod_hash=Program.to([]),
)
+1 -1
View File
@@ -1 +1 @@
ff02ffff01ff02ff5effff04ff02ffff04ffff04ff05ffff04ffff0bff2cff0580ffff04ff0bff80808080ffff04ffff02ff17ff2f80ffff04ff5fffff04ffff02ff2effff04ff02ffff04ff17ff80808080ffff04ffff0bff82027fff82057fff820b7f80ffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff820bffff80808080808080808080808080ffff04ffff01ffffffff81ca3dff46ff0233ffff3c04ff01ff0181cbffffff02ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff2cff3480ffff0bff22ffff0bff22ffff0bff2cff5c80ff0980ffff0bff22ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff0bffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff13ff80808080ff820b9f80ffff01ff02ff26ffff04ff02ffff04ffff02ff13ffff04ff5fffff04ff17ffff04ff2fffff04ff81bfffff04ff82017fffff04ff1bff8080808080808080ffff04ff82017fff8080808080ffff01ff088080ff0180ffff01ff02ffff03ff17ffff01ff02ffff03ffff20ff81bf80ffff0182017fffff01ff088080ff0180ffff01ff088080ff018080ff0180ffff04ffff04ff05ff2780ffff04ffff10ff0bff5780ff778080ff02ffff03ff05ffff01ff02ffff03ffff09ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff01818f80ffff01ff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ffff04ff81b9ff82017980ff808080808080ffff01ff02ff5affff04ff02ffff04ffff02ffff03ffff09ff11ff7880ffff01ff04ff78ffff04ffff02ff36ffff04ff02ffff04ff13ffff04ff29ffff04ffff0bff2cff5b80ffff04ff2bff80808080808080ff398080ffff01ff02ffff03ffff09ff11ff2480ffff01ff04ff24ffff04ffff0bff20ff2980ff398080ffff010980ff018080ff0180ffff04ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff04ffff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ff17ff808080808080ff80808080808080ff0180ffff01ff04ff80ffff04ff80ff17808080ff0180ffffff02ffff03ff05ffff01ff04ff09ffff02ff26ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff0bff22ffff0bff2cff5880ffff0bff22ffff0bff22ffff0bff2cff5c80ff0580ffff0bff22ffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff2cff058080ff0180ffff04ffff04ff28ffff04ff5fff808080ffff02ff7effff04ff02ffff04ffff04ffff04ff2fff0580ffff04ff5fff82017f8080ffff04ffff02ff7affff04ff02ffff04ff0bffff04ff05ffff01ff808080808080ffff04ff17ffff04ff81bfffff04ff82017fffff04ffff0bff8204ffffff02ff36ffff04ff02ffff04ff09ffff04ff820affffff04ffff0bff2cff2d80ffff04ff15ff80808080808080ff8216ff80ffff04ff8205ffffff04ff820bffff808080808080808080808080ff02ff2affff04ff02ffff04ff5fffff04ff3bffff04ffff02ffff03ff17ffff01ff09ff2dffff0bff27ffff02ff36ffff04ff02ffff04ff29ffff04ff57ffff04ffff0bff2cff81b980ffff04ff59ff80808080808080ff81b78080ff8080ff0180ffff04ff17ffff04ff05ffff04ff8202ffffff04ffff04ffff04ff24ffff04ffff0bff7cff2fff82017f80ff808080ffff04ffff04ff30ffff04ffff0bff81bfffff0bff7cff15ffff10ff82017fffff11ff8202dfff2b80ff8202ff808080ff808080ff138080ff80808080808080808080ff018080
ff02ffff01ff02ff5effff04ff02ffff04ffff04ff05ffff04ffff0bff2cff0580ffff04ff0bff80808080ffff04ffff02ff17ff2f80ffff04ff5fffff04ffff02ff2effff04ff02ffff04ff17ff80808080ffff04ffff0bff82027fff82057fff820b7f80ffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff820bffff80808080808080808080808080ffff04ffff01ffffffff81ca3dff46ff0233ffff3c04ff01ff0181cbffffff02ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff2cff3480ffff0bff22ffff0bff22ffff0bff2cff5c80ff0980ffff0bff22ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff0bffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff13ff80808080ff820b9f80ffff01ff02ff26ffff04ff02ffff04ffff02ff13ffff04ff5fffff04ff17ffff04ff2fffff04ff81bfffff04ff82017fffff04ff1bff8080808080808080ffff04ff82017fff8080808080ffff01ff088080ff0180ffff01ff02ffff03ff17ffff01ff02ffff03ffff20ff81bf80ffff0182017fffff01ff088080ff0180ffff01ff088080ff018080ff0180ffff04ffff04ff05ff2780ffff04ffff10ff0bff5780ff778080ff02ffff03ff05ffff01ff02ffff03ffff09ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff01818f80ffff01ff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ffff04ff81b9ff82017980ff808080808080ffff01ff02ff5affff04ff02ffff04ffff02ffff03ffff09ff11ff7880ffff01ff04ff78ffff04ffff02ff36ffff04ff02ffff04ff13ffff04ff29ffff04ffff0bff2cff5b80ffff04ff2bff80808080808080ff398080ffff01ff02ffff03ffff09ff11ff2480ffff01ff04ff24ffff04ffff0bff20ff2980ff398080ffff010980ff018080ff0180ffff04ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff04ffff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ff17ff808080808080ff80808080808080ff0180ffff01ff04ff80ffff04ff80ff17808080ff0180ffffff02ffff03ff05ffff01ff04ff09ffff02ff26ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff0bff22ffff0bff2cff5880ffff0bff22ffff0bff22ffff0bff2cff5c80ff0580ffff0bff22ffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff2cff058080ff0180ffff04ffff04ff28ffff04ff5fff808080ffff02ff7effff04ff02ffff04ffff04ffff04ff2fff0580ffff04ff5fff82017f8080ffff04ffff02ff7affff04ff02ffff04ff0bffff04ff05ffff01ff808080808080ffff04ff17ffff04ff81bfffff04ff82017fffff04ffff0bff8204ffffff02ff36ffff04ff02ffff04ff09ffff04ff820affffff04ffff0bff2cff2d80ffff04ff15ff80808080808080ff8216ff80ffff04ff8205ffffff04ff820bffff808080808080808080808080ff02ff2affff04ff02ffff04ff5fffff04ff3bffff04ffff02ffff03ff17ffff01ff09ff2dffff0bff27ffff02ff36ffff04ff02ffff04ff29ffff04ff57ffff04ffff0bff2cff81b980ffff04ff59ff80808080808080ff81b78080ff8080ff0180ffff04ff17ffff04ff05ffff04ff8202ffffff04ffff04ffff04ff24ffff04ffff0bff7cff2fff82017f80ff808080ffff04ffff04ff30ffff04ffff0bff81bfffff0bff7cff15ffff10ff82017fffff11ff8202dfff2b80ff8202ff808080ff808080ff138080ff80808080808080808080ff018080
+53 -80
View File
@@ -5,17 +5,19 @@
(mod
(
MY_PUBKEY ; the public key of the owner used for signing transactions
INNER_PUZZLE ; Standard P2 inner puzzle, used to record the ownership of the DID.
RECOVERY_DID_LIST_HASH ; the list of DIDs that can send messages to you for recovery we store only the hash so that we don't have to reveal every time we make a message spend
NUM_VERIFICATIONS_REQUIRED ; how many of the above list are required for a recovery
Truths ; Truths are sent from the singleton layer
mode ; this indicates which spend mode we want. Create message, recover, or self-destruct
new_amount ; DIDs can receive payments so when we recreate ourselves sometimes we want to change our amount
message ; this is a list of messages when creating a message spend, or a new puzhash when recovering or self destructing
new_inner_puzhash ; this is used during the message creation spend to optionally give ourselves a new inner puzzle - this is useful for updating our recovery list
SINGLETON_STRUCT ; my singleton_struct, formerly a Truth - ((SINGLETON_MOD_HASH, (LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
METADATA ; Customized metadata, e.g KYC info
mode ; this indicates which spend mode we want. 0. Recovery mode 1. Run INNER_PUZZLE with p2_solution
my_amount_or_inner_solution ; In mode 0, we use this to recover our coin and assert it is our actual amount
; In mode 1 this is the solution of the inner P2 puzzle, only required in the create message mode and transfer mode.
new_inner_puzhash ; In recovery mode, this will be the new wallet DID puzzle hash
parent_innerpuzhash_amounts_for_recovery_ids ; during a recovery we need extra information about our recovery list coins
pubkey ; this is the new pubkey used for a recovery
recovery_list_reveal ; this is the reveal of the stored list of DIDs approved for recovery
my_id ; my coin ID
)
;message is the new puzzle in the recovery and standard spend cases
@@ -24,7 +26,6 @@
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(include singleton_truths.clib)
; takes a lisp tree and returns the hash of it
(defun sha256tree1 (TREE)
@@ -50,7 +51,7 @@
)
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
; return the full puzzlehash for a singleton with the innerpuzzle curried in
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
@@ -59,104 +60,76 @@
)
)
(defmacro create_new_coin (amount new_puz)
(qq (c CREATE_COIN (c (unquote new_puz) (c (unquote amount) ()))))
)
; this loops over our identities to check list, and checks if we have been given parent information for this identity
; the reason for this is because we might only require 3/5 of the IDs give approval messages for a recovery
; if we have the information for an identity then we create a consume message using that information
(defun check_messages_from_identities (SINGLETON_STRUCT num_verifications_required identities my_id output new_puz parent_innerpuzhash_amounts_for_recovery_ids pubkey num_verifications)
(defun check_messages_from_identities (SINGLETON_STRUCT num_verifications_required identities my_id new_puz parent_innerpuzhash_amounts_for_recovery_ids pubkey num_verifications)
(if identities
(if (f parent_innerpuzhash_amounts_for_recovery_ids)
; if we have parent information then we should create a consume coin condition
(check_messages_from_identities
SINGLETON_STRUCT
num_verifications_required
(r identities)
my_id
(c
(create_consume_message
; create coin_id from DID
(create_coin_ID_for_recovery
SINGLETON_STRUCT
(f identities)
(f (f parent_innerpuzhash_amounts_for_recovery_ids))
(f (r (f parent_innerpuzhash_amounts_for_recovery_ids)))
(f (r (r (f parent_innerpuzhash_amounts_for_recovery_ids)))))
my_id
new_puz
pubkey)
output)
new_puz
(r parent_innerpuzhash_amounts_for_recovery_ids)
pubkey
(+ num_verifications 1)
(c
(create_consume_message
; create coin_id from DID
(create_coin_ID_for_recovery
SINGLETON_STRUCT
(f identities)
(f (f parent_innerpuzhash_amounts_for_recovery_ids))
(f (r (f parent_innerpuzhash_amounts_for_recovery_ids)))
(f (r (r (f parent_innerpuzhash_amounts_for_recovery_ids)))))
my_id
new_puz
pubkey
)
(check_messages_from_identities
SINGLETON_STRUCT
num_verifications_required
(r identities)
my_id
new_puz
(r parent_innerpuzhash_amounts_for_recovery_ids)
pubkey
(+ num_verifications 1)
)
)
; if no parent information found for this identity, move on to next in list
(check_messages_from_identities
SINGLETON_STRUCT
(r identities)
my_id
output
new_puz
(r parent_innerpuzhash_amounts_for_recovery_ids)
pubkey
num_verifications
)
)
;if we're out of identites to check for, return our output
(if (> num_verifications num_verifications_required)
(c (list AGG_SIG_UNSAFE pubkey new_puz) output)
(if (= num_verifications num_verifications_required)
(c (list AGG_SIG_UNSAFE pubkey new_puz) output)
(x)
)
;if we're out of identites to check for, check we have enough
(if (> num_verifications (- num_verifications_required 1))
(list (list AGG_SIG_UNSAFE pubkey new_puz) )
(x)
)
)
)
; for a list of messages in the format (type . message) create a message
; type 0 is 0 value coin
; type 1 is coin announcement
; type 2 is puzzle announcement
(defun create_messages (messages)
(if messages
(c
(if (f (f messages))
(list (if (= (f (f messages)) 1) CREATE_COIN_ANNOUNCEMENT CREATE_PUZZLE_ANNOUNCEMENT) (r (f messages)))
(list CREATE_COIN (r (f messages)) 0)
)
(create_messages (r messages))
)
()
)
)
;Spend modes:
;0 = exit spend
;1 = create messages and recreate singleton
;2 = recovery
;0 = recovery
;1 = run the INNER_PUZZLE
;MAIN
(if mode
(if (= mode 1)
; mode one - create messages and recreate singleton
(c (list CREATE_COIN new_inner_puzhash new_amount) (c (list AGG_SIG_ME MY_PUBKEY (sha256tree1 (list new_inner_puzhash new_amount message))) (create_messages message)))
; mode two - recovery
; check that recovery list is not empty
(if recovery_list_reveal
(if (= (sha256tree1 recovery_list_reveal) RECOVERY_DID_LIST_HASH)
(check_messages_from_identities (singleton_struct_truth Truths) NUM_VERIFICATIONS_REQUIRED recovery_list_reveal (my_id_truth Truths) (list (create_new_coin new_amount message)) message parent_innerpuzhash_amounts_for_recovery_ids pubkey 0)
(x)
)
(x)
)
)
; mode zero - exit spend
(list (list CREATE_COIN 0x00 -113) (list CREATE_COIN message new_amount) (list AGG_SIG_ME MY_PUBKEY (sha256tree1 (list new_amount message))))
)
; mode 1 - run INNER_PUZZLE
(a INNER_PUZZLE my_amount_or_inner_solution)
; mode 0 - recovery
(if (all (= (sha256tree1 recovery_list_reveal) RECOVERY_DID_LIST_HASH) (> NUM_VERIFICATIONS_REQUIRED 0))
(c (list ASSERT_MY_AMOUNT my_amount_or_inner_solution)
(c (list CREATE_COIN new_inner_puzhash my_amount_or_inner_solution (list new_inner_puzhash))
(c (list ASSERT_MY_COIN_ID my_id)
(check_messages_from_identities SINGLETON_STRUCT NUM_VERIFICATIONS_REQUIRED recovery_list_reveal my_id new_inner_puzhash parent_innerpuzhash_amounts_for_recovery_ids pubkey 0)
)
)
)
(x)
)
)
)
+1 -1
View File
@@ -1 +1 @@
ff02ffff01ff02ffff03ff5fffff01ff02ffff03ffff09ff5fffff010180ffff01ff04ffff04ff24ffff04ff8202ffffff04ff81bfff80808080ffff04ffff04ff20ffff04ff05ffff04ffff02ff7effff04ff02ffff04ffff04ff8202ffffff04ff81bfffff04ff82017fff80808080ff80808080ff80808080ffff02ff36ffff04ff02ffff04ff82017fff808080808080ffff01ff02ffff03ff8217ffffff01ff02ffff03ffff09ffff02ff7effff04ff02ffff04ff8217ffff80808080ff0b80ffff01ff02ff3affff04ff02ffff04ff8201efffff04ff17ffff04ff8217ffffff04ff818fffff04ffff04ffff04ff24ffff04ff82017fffff04ff81bfff80808080ff8080ffff04ff82017fffff04ff8205ffffff04ff820bffffff01ff808080808080808080808080ffff01ff088080ff0180ffff01ff088080ff018080ff0180ffff01ff04ffff04ff24ffff01ff00ff818f8080ffff04ffff04ff24ffff04ff82017fffff04ff81bfff80808080ffff04ffff04ff20ffff04ff05ffff04ffff02ff7effff04ff02ffff04ffff04ff81bfffff04ff82017fff808080ff80808080ff80808080ff8080808080ff0180ffff04ffff01ffffffff3231ff3d02ffff333cff3eff0401ffffff0102ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff32ffff0bff7cff5c80ffff0bff32ffff0bff32ffff0bff7cff2280ff0980ffff0bff32ff0bffff0bff7cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ff82027fffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fffff04ffff04ffff04ff28ffff04ffff0bffff0bffff02ff26ffff04ff02ffff04ff05ffff04ff27ffff04ff82047fffff04ff820a7fffff04ff82167fff8080808080808080ffff02ff7effff04ff02ffff04ffff02ff2effff04ff02ffff04ff2fffff04ff81bfffff04ff8202ffff808080808080ff8080808080ff2f80ff808080ff5f80ffff04ff81bfffff04ff82037fffff04ff8202ffffff04ffff10ff8205ffffff010180ff808080808080808080808080ffff01ff02ff3affff04ff02ffff04ff05ffff04ff37ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff04ff8202ffffff04ff8205ffff808080808080808080808080ff0180ffff01ff02ffff03ffff15ff8205ffff0b80ffff01ff04ffff04ff30ffff04ff8202ffffff04ff81bfff80808080ff5f80ffff01ff02ffff03ffff09ff8205ffff0b80ffff01ff04ffff04ff30ffff04ff8202ffffff04ff81bfff80808080ff5f80ffff01ff088080ff018080ff018080ff0180ffffff0bff17ffff02ff5effff04ff02ffff04ff09ffff04ff2fffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff0bff1d8080ff80808080ff808080808080ff5f80ff02ffff03ff05ffff01ff04ffff02ffff03ff11ffff01ff04ffff02ffff03ffff09ff11ffff010180ffff0134ffff012c80ff0180ffff04ff19ff808080ffff01ff04ff24ffff04ff19ffff01ff8080808080ff0180ffff02ff36ffff04ff02ffff04ff0dff8080808080ff8080ff0180ffff04ffff0101ffff04ffff04ff34ffff04ff05ff808080ffff04ffff04ff30ffff04ff17ffff04ff0bff80808080ff80808080ffff0bff32ffff0bff7cff3880ffff0bff32ffff0bff32ffff0bff7cff2280ff0580ffff0bff32ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff7cff7c80ff8080808080ffff0bff7cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
ff02ffff01ff02ffff03ff81bfffff01ff02ff05ff82017f80ffff01ff02ffff03ffff22ffff09ffff02ff7effff04ff02ffff04ff8217ffff80808080ff0b80ffff15ff17ff808080ffff01ff04ffff04ff28ffff04ff82017fff808080ffff04ffff04ff34ffff04ff8202ffffff04ff82017fffff04ffff04ff8202ffff8080ff8080808080ffff04ffff04ff38ffff04ff822fffff808080ffff02ff26ffff04ff02ffff04ff2fffff04ff17ffff04ff8217ffffff04ff822fffffff04ff8202ffffff04ff8205ffffff04ff820bffffff01ff8080808080808080808080808080ffff01ff088080ff018080ff0180ffff04ffff01ffffffff313dff4946ffff0233ff3c04ffffff0101ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff22ff3c80ffff0bff2affff0bff2affff0bff22ff3280ff0980ffff0bff2aff0bffff0bff22ff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff17ffff01ff02ffff03ff82013fffff01ff04ffff04ff30ffff04ffff0bffff0bffff02ff36ffff04ff02ffff04ff05ffff04ff27ffff04ff82023fffff04ff82053fffff04ff820b3fff8080808080808080ffff02ff7effff04ff02ffff04ffff02ff2effff04ff02ffff04ff2fffff04ff5fffff04ff82017fff808080808080ff8080808080ff2f80ff808080ffff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fffff04ff5fffff04ff8201bfffff04ff82017fffff04ffff10ff8202ffffff010180ff808080808080808080808080ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff37ffff04ff2fffff04ff5fffff04ff8201bfffff04ff82017fffff04ff8202ffff8080808080808080808080ff0180ffff01ff02ffff03ffff15ff8202ffffff11ff0bffff01018080ffff01ff04ffff04ff20ffff04ff82017fffff04ff5fff80808080ff8080ffff01ff088080ff018080ff0180ff0bff17ffff02ff5effff04ff02ffff04ff09ffff04ff2fffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff0bff1d8080ff80808080ff808080808080ff5f80ffff04ffff0101ffff04ffff04ff2cffff04ff05ff808080ffff04ffff04ff20ffff04ff17ffff04ff0bff80808080ff80808080ffff0bff2affff0bff22ff2480ffff0bff2affff0bff2affff0bff22ff3280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff22ff2280ff8080808080ffff0bff22ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
@@ -1 +1 @@
ef41902d9964f6050f87de98b5c4e34512b7d2abded3fe700f7850ff20323bf2
33143d2bef64f14036742673afd158126b94284b4530a28c354fac202b0c910e
+25
View File
@@ -0,0 +1,25 @@
(
(defun search_list_then_resume (key json_object result)
(if result
result
(search_for_value_given_key key (r json_object))
)
)
(defun search_for_value_given_key (key json_object)
(if json_object
(if (l (f from_json_dict)) ; if we are hitting an unlabelled item (atom), just move on
(if (= (f (f json_object)) key) ; if we have found our key
(r (f json_object)) ; return the value
(if (l (r (f json_object))) ; otherwise check if we have hit another dictionary
(search_list_then_resume key json_object (search_for_value_given_key key (r (f json_object)))) ; check new dictionary
(search_for_value_given_key key (r json_object))
)
)
(search_for_value_given_key key (r json_object))
)
()
)
)
)
+85
View File
@@ -0,0 +1,85 @@
(mod (
NFT_MOD_HASH
SINGLETON_STRUCT ; ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
CURRENT_OWNER_DID
TRANSFER_PROGRAM_MOD_HASH
TRANSFER_PROGRAM_CURRY_PARAMS
METADATA
my_did_inner_hash
new_did
new_did_inner_hash
transfer_program_reveal
transfer_program_solution
)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(include singleton_truths.clib)
; takes a lisp tree and returns the hash of it
(defun sha256tree1 (TREE)
(if (l TREE)
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
(sha256 ONE TREE)
)
)
(defun-inline nft_puzzle_hash (NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH next_owner_did transfer_program_curry_params metadata)
(puzzle-hash-of-curried-function NFT_MOD_HASH
(sha256tree1 metadata)
(sha256tree1 transfer_program_curry_params)
(sha256 ONE TRANSFER_PROGRAM_MOD_HASH)
(sha256 ONE next_owner_did)
(sha256tree1 SINGLETON_STRUCT)
(sha256 ONE NFT_MOD_HASH)
)
)
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
inner_puzzle_hash
(sha256tree1 SINGLETON_STRUCT)
)
)
; if we find an update, recreate ourselves with the update
; otherwise recreate ourselves as we are
(defun parse_transfer_prog_output (NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did output found_update)
(if output
(if (= (f (f output)) -22)
(c
(list CREATE_COIN (nft_puzzle_hash NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH new_did (f (r (r (f output)))) (f (r (f output)))) ONE (list new_did))
(parse_transfer_prog_output NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did (r output) 1)
)
(c (f output) (parse_transfer_prog_output NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did (r output) found_update))
)
(if found_update ; if we're at the end of the loop, check if we've already recreated ourselves or not
()
(list (list CREATE_COIN (nft_puzzle_hash NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH new_did TRANSFER_PROGRAM_CURRY_PARAMS METADATA) ONE (list new_did)))
)
)
)
; main
(c
(list ASSERT_MY_AMOUNT ONE)
(if new_did
(if (= (sha256tree1 transfer_program_reveal) TRANSFER_PROGRAM_MOD_HASH)
(c
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c CURRENT_OWNER_DID (r (r SINGLETON_STRUCT)))) my_did_inner_hash) (sha256tree1 (list transfer_program_solution new_did))))
; (METADATA CURRY_PARAMS SINGLETON_STRUCT current_owner trade_price_list my_did_inner_hash new_did new_did_inner_hash my_nft_id solution)
(parse_transfer_prog_output NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did (a transfer_program_reveal (list METADATA TRANSFER_PROGRAM_CURRY_PARAMS SINGLETON_STRUCT CURRENT_OWNER_DID my_did_inner_hash new_did new_did_inner_hash transfer_program_solution)) 0)
)
(x)
)
(list
(list CREATE_COIN (nft_puzzle_hash NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH CURRENT_OWNER_DID TRANSFER_PROGRAM_CURRY_PARAMS METADATA) ONE (list CURRENT_OWNER_DID))
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c CURRENT_OWNER_DID (r (r SINGLETON_STRUCT)))) my_did_inner_hash) 'a'))
(list CREATE_PUZZLE_ANNOUNCEMENT CURRENT_OWNER_DID)
)
)
)
)
@@ -0,0 +1 @@
ff02ffff01ff04ffff04ff10ffff04ff12ff808080ffff02ffff03ff8202ffffff01ff02ffff03ffff09ffff02ff3effff04ff02ffff04ff820bffff80808080ff2f80ffff01ff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff02ff3effff04ff02ffff04ffff04ff8217ffffff04ff8202ffff808080ff8080808080ff808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff2fffff04ff5fffff04ff81bfffff04ff8202ffffff04ffff02ff820bffffff04ff81bfffff04ff5fffff04ff0bffff04ff17ffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff8217ffff80808080808080808080ffff01ff808080808080808080808080ffff01ff088080ff0180ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff81bfff80808080ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff0bff12ff2f80ffff04ffff0bff12ff1780ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff17ff8080ff8080808080ffff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff016180ff808080ffff04ffff04ff2cffff04ff17ff808080ff8080808080ff018080ffff04ffff01ffffff49ff3f02ff33ff3e04ffff01ff0102ffffff02ffff03ff05ffff01ff02ff26ffff04ff02ffff04ff0dffff04ffff0bff3affff0bff12ff3c80ffff0bff3affff0bff3affff0bff12ff2a80ff0980ffff0bff3aff0bffff0bff12ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff82017fffff01ff02ffff03ffff09ff82047fffff0181ea80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff820a7fff80808080ffff04ffff02ff3effff04ff02ffff04ff82167fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff01ff018080808080808080808080ffff01ff04ff82027fffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff04ff8202ffff80808080808080808080808080ff0180ffff01ff02ffff03ff8202ffff80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff02ff3effff04ff02ffff04ff2fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ff808080ff018080ff0180ffff0bff3affff0bff12ff3880ffff0bff3affff0bff3affff0bff12ff2a80ff0580ffff0bff3affff02ff26ffff04ff02ffff04ff07ffff04ffff0bff12ff1280ff8080808080ffff0bff12ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bff12ff058080ff0180ff018080
@@ -0,0 +1 @@
ae8ee2bf9e0bcc189d4d8efa3c8281235981abd75ade592ba71930aa6881f820
@@ -0,0 +1,20 @@
(mod (CURRENT_METADATA solution)
; once we find 'u' we don't need to continue looping
(defun add_url (METADATA new_url)
(if METADATA
(if (= (f (f METADATA)) 'u')
(c (c 'u' (c new_url (r (f METADATA)))) (r METADATA))
(c (f METADATA) (add_url (r METADATA) new_url))
)
()
)
)
; main
; returns (new_metadata conditions)
(if solution
(list (add_url CURRENT_METADATA solution) 0)
(list CURRENT_METADATA 0)
)
)
@@ -0,0 +1 @@
ff02ffff01ff02ffff03ff0bffff01ff04ffff02ff02ffff04ff02ffff04ff05ffff04ff0bff8080808080ffff01ff808080ffff01ff04ff05ffff01ff80808080ff0180ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080
@@ -0,0 +1 @@
3df9de54667a96f32eba322635f14d3474edadf23f396ba5a3e2e077a89a682a
@@ -0,0 +1,21 @@
(mod (CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH solution)
; METADATA and METADATA_UPDATER_PUZZLE_HASH are passed in as truths from the layer above
; This program returns ((new_metadata new_metadata_updater_puzhash) conditions)
; once we find 'u' we don't need to continue looping
(defun add_url (METADATA new_url)
(if METADATA
(if (= (f (f METADATA)) 'u')
(c (c 'u' (c new_url (r (f METADATA)))) (r METADATA))
(c (f METADATA) (add_url (r METADATA) new_url))
)
()
)
)
; main
; returns ((new_metadata new_metadata_updater_puzhash) conditions)
(list (list (if solution (add_url CURRENT_METADATA solution) CURRENT_METADATA) METADATA_UPDATER_PUZZLE_HASH) 0)
)
@@ -0,0 +1 @@
ff02ffff01ff04ffff04ffff02ffff03ff17ffff01ff02ff02ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff010580ff0180ffff04ff0bff808080ffff01ff808080ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080
@@ -0,0 +1 @@
81970d352e6a39a241eaf8ca510a0e669e40d778ba612621c60a50ef6cf29c7b
@@ -0,0 +1,83 @@
(mod
(
; SINGLETON_STRUCT is ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
SINGLETON_STRUCT
; CURRY_PARAMS are: (ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH)
CURRY_PARAMS
; DID of the current owner
CURRENT_OWNER
; Royalty program puzzle hash
ROYALTY_PROG_PUZHASH
; DID inner puzzle hash of the current owner
current_did_puzhash
; New NFT owner's DID
new_owner
; DID inner puzzle hash of the new owner
new_did_puzhash
; trade_prices_list is a list of (amount type) pairs
; type 0 is standard chia
; any other type is a bytes32 mod hash of a launcher
trade_prices_list
)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(include sha256tree.clib)
(defconstant CHANGE_OWNERSHIP -22)
(defconstant TEN_THOUSAND 10000)
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
inner_puzzle_hash
(sha256tree SINGLETON_STRUCT)
)
)
; Given a singleton ID, generate the singleton struct
(defun-inline get_singleton_struct (SINGLETON_STRUCT singleton_id)
(c (f SINGLETON_STRUCT) (c singleton_id (r (r SINGLETON_STRUCT))))
)
(defun-inline cat_settlement_puzzle_hash (CAT_MOD_HASH tail_hash SETTLEMENT_MOD_HASH)
(puzzle-hash-of-curried-function CAT_MOD_HASH
SETTLEMENT_MOD_HASH
(sha256 1 tail_hash)
(sha256 1 CAT_MOD_HASH)
)
)
(defun round_down_to_even (value)
(if (logand value 1) (- value 1) value)
)
(defun-inline calculate_percentage (amount percentage)
(f (divmod (* amount percentage) TEN_THOUSAND))
)
; Loop of the trade prices list and either assert a puzzle announcement or generate xch
(defun parse_trade_prices_list ((ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) trade_prices_list my_nft_id)
(if trade_prices_list
(c
(if (r (f trade_prices_list))
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (cat_settlement_puzzle_hash CAT_MOD_HASH (f (r (f trade_prices_list))) SETTLEMENT_MOD_HASH) (sha256tree (c my_nft_id (list (list ROYALTY_ADDRESS (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))))))
(list CREATE_COIN ROYALTY_ADDRESS (round_down_to_even (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))
)
(parse_trade_prices_list (list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) (r trade_prices_list) my_nft_id)
)
()
)
)
; main
(list
(c (list CHANGE_OWNERSHIP new_owner)
(c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (get_singleton_struct SINGLETON_STRUCT CURRENT_OWNER) current_did_puzhash) (f (r SINGLETON_STRUCT)) (sha256tree royalty_prog_solution) new_owner))
(c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (get_singleton_struct SINGLETON_STRUCT new_owner) new_did_puzhash) (f (r SINGLETON_STRUCT)) (sha256tree royalty_prog_solution)))
(parse_trade_prices_list CURRY_PARAMS trade_prices_list (f (r SINGLETON_STRUCT)))
)
)
)
)
)
@@ -0,0 +1 @@
ff02ffff01ff04ffff04ffff04ff38ffff04ff81bfff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff5fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff17ff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e808080ff81bf80ff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff81bfff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e80808080ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff8202ffffff04ff15ff808080808080808080ff8080ffff04ffff01ffffff3fff0281eaffff3304ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff3480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff5dffff04ff2dffff04ffff0bffff0101ff5380ffff04ffff0bffff0101ff5d80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff24ffff04ff09ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
@@ -0,0 +1 @@
03557552e0bf8b200d8be8089f54786195ab8c07e3e07c39641f2f4f1fc2e48e
+99
View File
@@ -0,0 +1,99 @@
(mod (
NFT_STATE_LAYER_MOD_HASH
METADATA
METADATA_UPDATER_PUZZLE_HASH
INNER_PUZZLE
solution ; either to inner puzzle or metadata updater
my_amount
)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(defun sha256tree1
(TREE)
(if (l TREE)
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
(sha256 1 TREE)
)
)
(defun-inline nft_state_layer_puzzle_hash (NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH inner_puzzle_hash)
(puzzle-hash-of-curried-function NFT_STATE_LAYER_MOD_HASH
inner_puzzle_hash
(sha256 ONE METADATA_UPDATER_PUZZLE_HASH)
(sha256tree1 METADATA)
(sha256 ONE NFT_STATE_LAYER_MOD_HASH)
)
)
(defun wrap_odd_create_coins (NFT_STATE_LAYER_MOD_HASH ((METADATA METADATA_UPDATER_PUZZLE_HASH) conditions) my_amount new_create_coin_condition)
(if conditions
(if (= (f (f conditions)) CREATE_COIN)
(if (logand (f (r (r (f conditions)))) ONE)
(wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount (f conditions))
(c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount new_create_coin_condition))
)
(if (> (f (f conditions)) 0)
(c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount new_create_coin_condition))
(wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount new_create_coin_condition)
)
)
(if new_create_coin_condition
(list (c CREATE_COIN
(c (nft_state_layer_puzzle_hash
NFT_STATE_LAYER_MOD_HASH METADATA
METADATA_UPDATER_PUZZLE_HASH
(f (r new_create_coin_condition)))
(c my_amount
(r (r (r new_create_coin_condition)))
)
)
))
(x)
)
)
)
; take two lists and merge them into one
(defun merge_list (list_a list_b)
(if list_a
(c (f list_a) (merge_list (r list_a) list_b))
list_b
)
)
(defun check_for_metadata_prog_reveal (METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions_loop)
(if conditions_loop
(if (= (f (f conditions_loop)) -24)
(if (= (sha256tree1 (f (r (f conditions_loop)))) METADATA_UPDATER_PUZZLE_HASH)
(a (f (r (f conditions_loop))) (list CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (r (f conditions_loop))))))
(x)
)
(check_for_metadata_prog_reveal METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA (r conditions_loop))
)
(list (list CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH) 0)
)
)
(defun process_metadata_updater (metadata_result conditions)
(c (f metadata_result) (list (merge_list (f (r metadata_result)) conditions)))
)
(defun metadata_updater_loader (METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions)
(process_metadata_updater (check_for_metadata_prog_reveal METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions) conditions)
)
; main
(c
(list ASSERT_MY_AMOUNT my_amount)
(wrap_odd_create_coins
NFT_STATE_LAYER_MOD_HASH
(metadata_updater_loader
METADATA_UPDATER_PUZZLE_HASH METADATA (a INNER_PUZZLE solution)
)
my_amount
0
)
)
)
@@ -0,0 +1 @@
ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfffff01ff8080808080808080ffff04ffff01ffffff49ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ffff0181e880ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff05ffff04ff820167ff8080808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ffff04ff0bffff04ff05ff808080ffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff2bffff01ff02ffff03ffff09ff818bff3880ffff01ff02ffff03ffff18ff8202cbff3480ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff4bff80808080808080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff808080808080808080ff0180ffff01ff02ffff03ffff15ff818bff8080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff8080808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff8080808080808080ff018080ff0180ffff01ff02ffff03ff2fffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff81afffff04ffff0bff34ff5380ffff04ffff02ff2effff04ff02ffff04ff23ff80808080ffff04ffff0bff34ff0580ff8080808080808080ffff04ff17ff8201ef808080ff8080ffff01ff088080ff018080ff0180ff018080
@@ -0,0 +1 @@
dd8135d546e291df295b376aa89fc409c8c50d7f655d1ff4e845637901bc2f8f
@@ -0,0 +1,139 @@
(mod (METADATA CURRY_PARAMS SINGLETON_STRUCT current_owner my_did_inner_hash new_did new_did_inner_hash solution)
; METADATA and CURRY_PARAMS are not actually curried into the mod hash but are curried into the NFT layer and passed in
; This effectively achieves the same result - fixing the values - but means that the transfer program mod never changes
; CURRY_PARAMS are: (ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH)
; SINGLETON_STRUCT is ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
; For this transfer program solution is (trade_prices_list new_url)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(defconstant TEN_THOUSAND 10000)
; trade_prices_list is a list of (amount type) pairs
; type 0 is standard chia
; any other type is a bytes32 mod hash of a launcher
(defun-inline cat_settlement_puzzle_hash (CAT_MOD_HASH tail_hash SETTLEMENT_MOD_HASH)
(puzzle-hash-of-curried-function CAT_MOD_HASH
SETTLEMENT_MOD_HASH
(sha256 ONE tail_hash)
(sha256 ONE CAT_MOD_HASH)
)
)
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
inner_puzzle_hash
(sha256tree1 SINGLETON_STRUCT)
)
)
(defun round_down_to_even (value)
(if (logand value ONE) (- value ONE) value)
)
(defun-inline calculate_percentage (amount percentage)
(f (divmod (* amount percentage) TEN_THOUSAND))
)
(defun sha256tree1 (TREE)
(if (l TREE)
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
(sha256 ONE TREE)
)
)
; Loop of the trade prices list and either assert a puzzle announcement or generate xch
(defun parse_trade_prices_list ((ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) trade_prices_list my_nft_id)
(if trade_prices_list
(c
(if (r (f trade_prices_list))
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (cat_settlement_puzzle_hash CAT_MOD_HASH (f (r (f trade_prices_list))) SETTLEMENT_MOD_HASH) (sha256tree1 (c my_nft_id (list (list ROYALTY_ADDRESS (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))))))
(list CREATE_COIN ROYALTY_ADDRESS (round_down_to_even (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))
)
(parse_trade_prices_list (list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) (r trade_prices_list) my_nft_id)
)
()
)
)
; if we are part of a complex trade then we assert an announcement from the recipient of the trade_price and then parse the trade price list
(defun generate_conditions_for_trade (
SINGLETON_STRUCT
CURRENT_OWNER_DID
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
trade_prices_list
trade_prices_list_hash
)
(c (list CREATE_PUZZLE_ANNOUNCEMENT trade_prices_list_hash)
(c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c new_did (r (r SINGLETON_STRUCT)))) new_did_inner_hash) trade_prices_list_hash (f (r SINGLETON_STRUCT))))
(parse_trade_prices_list CURRY_PARAMS trade_prices_list (f (r SINGLETON_STRUCT)))
)
)
)
; once we find 'u' we don't need to continue looping
(defun add_url (METADATA new_url)
(if METADATA
(if (= (f (f METADATA)) 'u')
(c (c 'u' (c new_url (r (f METADATA)))) (r METADATA))
(c (f METADATA) (add_url (r METADATA) new_url))
)
()
)
)
(defun check_if_add_url (METADATA CURRY_PARAMS solution output)
(if solution
(c (list -22 (add_url METADATA solution) CURRY_PARAMS) output)
output
)
)
(defun check_if_gift (
SINGLETON_STRUCT
CURRENT_OWNER_DID
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
trade_prices_list
)
(if trade_prices_list
(generate_conditions_for_trade
SINGLETON_STRUCT
CURRENT_OWNER_DID
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
trade_prices_list
(sha256tree1 trade_prices_list)
)
(list (list CREATE_PUZZLE_ANNOUNCEMENT 0))
)
)
; main
(check_if_add_url
METADATA
CURRY_PARAMS
(f (r solution))
(check_if_gift
SINGLETON_STRUCT
current_owner
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
(f solution)
)
)
)
@@ -0,0 +1 @@
ff02ffff01ff02ff2affff04ff02ffff04ff05ffff04ff0bffff04ff820affffff04ffff02ff3affff04ff02ffff04ff17ffff04ff2fffff04ff0bffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffff80808080808080808080ff80808080808080ffff04ffff01ffffffff3f02ff333effff0401ff01ff82271002ffffffff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff22ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff7cffff0bff34ff2480ffff0bff7cffff0bff7cffff0bff34ff2c80ff0980ffff0bff7cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff04ffff04ffff0181eaffff04ffff02ff22ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff04ff0bff80808080ff2f80ffff012f80ff0180ff02ffff03ff82017fffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ffff02ff7effff04ff02ffff04ff82017fff80808080ff8080808080808080808080ffff01ff04ffff04ff38ffff01ff808080ff808080ff0180ffffff04ffff04ff38ffff04ff8202ffff808080ffff04ffff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff81bfffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff5fff1d8080ff80808080ff808080808080ff8202ffff1580ff808080ffff02ff36ffff04ff02ffff04ff17ffff04ff82017fffff04ff15ff8080808080808080ff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff5dffff04ff2dffff04ffff0bff34ff5380ffff04ffff0bff34ff5d80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff28ffff04ff09ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ffff0bff7cffff0bff34ff3080ffff0bff7cffff0bff7cffff0bff34ff2c80ff0580ffff0bff7cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080
@@ -0,0 +1 @@
519b806c31cce5280fa37695149521a301f13f5696d1fd1eeee1856c2d14965c
@@ -10,7 +10,7 @@
; synthetic_key_offset: a private key cryptographically generated using the hidden
; puzzle and as inputs `original_public_key`
;
; synthetic_public_key: the public key that is the sum of `original_public_key` and the
; SYNTHETIC_PUBLIC_KEY: the public key that is the sum of `original_public_key` and the
; public key corresponding to `synthetic_key_offset`
;
; original_public_key: a public key, where knowledge of the corresponding private key
@@ -23,17 +23,17 @@
(mod
; A puzzle should commit to `synthetic_public_key`
; A puzzle should commit to `SYNTHETIC_PUBLIC_KEY`
;
; The solution should pass in 0 for `original_public_key` if it wants to use
; an arbitrary `delegated_puzzle` (and `solution`) signed by the
; `synthetic_public_key` (whose corresponding private key can be calculated
; `SYNTHETIC_PUBLIC_KEY` (whose corresponding private key can be calculated
; if you know the private key for `original_public_key`)
;
; Or you can solve the hidden puzzle by revealing the `original_public_key`,
; the hidden puzzle in `delegated_puzzle`, and a solution to the hidden puzzle.
(synthetic_public_key original_public_key delegated_puzzle solution)
(SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle solution)
; "assert" is a macro that wraps repeated instances of "if"
; usage: (assert A0 A1 ... An R)
@@ -61,9 +61,9 @@
; "is_hidden_puzzle_correct" returns true iff the hidden puzzle is correctly encoded
(defun-inline is_hidden_puzzle_correct (synthetic_public_key original_public_key delegated_puzzle)
(defun-inline is_hidden_puzzle_correct (SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle)
(=
synthetic_public_key
SYNTHETIC_PUBLIC_KEY
(point_add
original_public_key
(pubkey_for_exp (sha256 original_public_key (sha256tree1 delegated_puzzle)))
@@ -73,19 +73,19 @@
; "possibly_prepend_aggsig" is the main entry point
(defun-inline possibly_prepend_aggsig (synthetic_public_key original_public_key delegated_puzzle conditions)
(defun-inline possibly_prepend_aggsig (SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle conditions)
(if original_public_key
(assert
(is_hidden_puzzle_correct synthetic_public_key original_public_key delegated_puzzle)
(is_hidden_puzzle_correct SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle)
conditions
)
(c (list AGG_SIG_ME synthetic_public_key (sha256tree1 delegated_puzzle)) conditions)
(c (list AGG_SIG_ME SYNTHETIC_PUBLIC_KEY (sha256tree1 delegated_puzzle)) conditions)
)
)
; main entry point
(possibly_prepend_aggsig
synthetic_public_key original_public_key delegated_puzzle
SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle
(a delegated_puzzle solution))
)
@@ -1 +1 @@
ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
+11
View File
@@ -0,0 +1,11 @@
(
;; hash a tree
;; This is used to calculate a puzzle hash given a puzzle program.
(defun sha256tree
(TREE)
(if (l TREE)
(sha256 2 (sha256tree (f TREE)) (sha256tree (r TREE)))
(sha256 1 TREE)
)
)
)
@@ -0,0 +1,124 @@
(mod (SINGLETON_STRUCT INNER_PUZZLE lineage_proof my_amount inner_solution)
;; SINGLETON_STRUCT = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
; SINGLETON_STRUCT, INNER_PUZZLE are curried in by the wallet
; EXAMPLE SOLUTION '(0xfadeddab 0xdeadbeef 1 (0xdeadbeef 200) 50 ((51 0xfadeddab 100) (60 "trash") (51 deadbeef 0)))'
; This puzzle is a wrapper around an inner smart puzzle which guarantees uniqueness.
; It takes its singleton identity from a coin with a launcher puzzle which guarantees that it is unique.
(include condition_codes.clvm)
(include curry-and-treehash.clinc) ; also imports the constant ONE == 1
(include singleton_truths.clib)
(defmacro assert items
(if (r items)
(list if (f items) (c assert (r items)) (q . (x)))
(f items)
)
)
(defmacro and ARGS
(if ARGS
(qq (if (unquote (f ARGS))
(unquote (c and (r ARGS)))
()
))
1)
)
; takes a lisp tree and returns the hash of it
(defun sha256tree (TREE)
(if (l TREE)
(sha256 2 (sha256tree (f TREE)) (sha256tree (r TREE)))
(sha256 ONE TREE)))
(defun-inline mod_hash_for_singleton_struct (SINGLETON_STRUCT) (f SINGLETON_STRUCT))
(defun-inline launcher_id_for_singleton_struct (SINGLETON_STRUCT) (f (r SINGLETON_STRUCT)))
(defun-inline launcher_puzzle_hash_for_singleton_struct (SINGLETON_STRUCT) (r (r SINGLETON_STRUCT)))
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
(puzzle-hash-of-curried-function (mod_hash_for_singleton_struct SINGLETON_STRUCT)
inner_puzzle_hash
(sha256tree SINGLETON_STRUCT)
)
)
(defun-inline morph_condition (condition SINGLETON_STRUCT)
(c (f condition) (c (calculate_full_puzzle_hash SINGLETON_STRUCT (f (r condition))) (r (r condition))))
)
; Assert exactly one output with odd value exists - ignore it if value is -113
;; this function iterates over the output conditions from the inner puzzle & solution
;; and both checks that exactly one unique singleton child is created (with odd valued output),
;; and wraps the inner puzzle with this same singleton wrapper puzzle
;;
;; The special case where the output value is -113 means a child singleton is intentionally
;; *NOT* being created, thus forever ending this singleton's existence
(defun check_and_morph_conditions_for_singleton (SINGLETON_STRUCT conditions has_odd_output_been_found)
(if conditions
; check if it's an odd create coin
(if (and (= (f (f conditions)) CREATE_COIN) (logand (f (r (r (f conditions)))) ONE))
; check that we haven't already found one
(assert (not has_odd_output_been_found)
; then
(if (= (f (r (r (f conditions)))) -113)
; If it's the melt condition we don't bother prepending this condition
(check_and_morph_conditions_for_singleton SINGLETON_STRUCT (r conditions) ONE)
; If it isn't the melt condition, we morph it and prepend it
(c (morph_condition (f conditions) SINGLETON_STRUCT) (check_and_morph_conditions_for_singleton SINGLETON_STRUCT (r conditions) ONE))
)
)
(c (f conditions) (check_and_morph_conditions_for_singleton SINGLETON_STRUCT (r conditions) has_odd_output_been_found))
)
(assert has_odd_output_been_found ())
)
)
; assert that either the lineage proof is for a parent singleton, or, if it's for the launcher, verify it matched our launcher ID
; then return a condition asserting it actually is our parent ID
(defun verify_lineage_proof (SINGLETON_STRUCT parent_id is_not_launcher)
(assert (any is_not_launcher (= parent_id (launcher_id_for_singleton_struct SINGLETON_STRUCT)))
; then
(list ASSERT_MY_PARENT_ID parent_id)
)
)
; main
; if our value is not an odd amount then we are invalid
(assert (logand my_amount ONE)
; then
(c
(list ASSERT_MY_AMOUNT my_amount)
(c
; Verify the lineage proof by asserting our parent's ID
(verify_lineage_proof
SINGLETON_STRUCT
; calculate our parent's ID
(sha256
(parent_info_for_lineage_proof lineage_proof)
(if (is_not_eve_proof lineage_proof) ; The PH calculation changes based on the lineage proof
(calculate_full_puzzle_hash SINGLETON_STRUCT (puzzle_hash_for_lineage_proof lineage_proof)) ; wrap the innerpuz in a singleton
(launcher_puzzle_hash_for_singleton_struct SINGLETON_STRUCT) ; Use the static launcher puzzle hash
)
(if (is_not_eve_proof lineage_proof) ; The position of "amount" changes based on the type on lineage proof
(amount_for_lineage_proof lineage_proof)
(amount_for_eve_proof lineage_proof)
)
)
(is_not_eve_proof lineage_proof)
)
; finally check all of the conditions for a single odd output to wrap
(check_and_morph_conditions_for_singleton SINGLETON_STRUCT (a INNER_PUZZLE inner_solution) 0)
)
)
)
)
@@ -0,0 +1 @@
ff02ffff01ff02ffff03ffff18ff2fff3c80ffff01ff04ffff04ff10ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff0bff27ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff02ffff03ff77ffff0181b7ffff015780ff018080ffff04ff77ff808080808080ffff02ff26ffff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffff49ff4702ff33ff0401ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff02ffff03ffff02ffff03ffff09ff23ff1480ffff01ff02ffff03ffff18ff81b3ff3c80ffff01ff0101ff8080ff0180ff8080ff0180ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff3cff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff3cff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ff0bff2affff0bff3cff3880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff3cff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff28ffff04ff0bff808080ffff01ff088080ff0180ff018080
@@ -0,0 +1 @@
f1e8350cec62f8204aaf867cc3c12cae369f619258206616108c6cfd7be760b3
+17
View File
@@ -0,0 +1,17 @@
from typing import Any
from chia.types.blockchain_format.program import Program
def json_to_chialisp(json_data: Any) -> Any:
list_for_chialisp = []
if isinstance(json_data, list):
for value in json_data:
list_for_chialisp.append(json_to_chialisp(value))
else:
if isinstance(json_data, dict):
for key, value in json_data:
list_for_chialisp.append((key, json_to_chialisp(value)))
else:
list_for_chialisp = json_data
return Program.to(list_for_chialisp)
+1
View File
@@ -19,6 +19,7 @@ class WalletType(IntEnum):
RECOVERABLE = 7
DISTRIBUTED_ID = 8
POOLING_WALLET = 9
NFT = 10
class AmountWithPuzzlehash(TypedDict):
+5 -5
View File
@@ -206,7 +206,7 @@ class Wallet:
me=None,
coin_announcements: Optional[Set[bytes]] = None,
coin_announcements_to_assert: Optional[Set[bytes32]] = None,
puzzle_announcements: Optional[Set[bytes32]] = None,
puzzle_announcements: Optional[Set[bytes]] = None,
puzzle_announcements_to_assert: Optional[Set[bytes32]] = None,
fee=0,
) -> Program:
@@ -310,7 +310,7 @@ class Wallet:
max_send = await self.get_max_send_amount()
if total_amount > max_send:
raise ValueError(f"Can't send more than {max_send} in a single transaction")
self.log.debug("Got back max send amount: %s", max_send)
if coins is None:
coins = await self.select_coins(uint64(total_amount))
assert len(coins) > 0
@@ -428,6 +428,7 @@ class Wallet:
else:
non_change_amount = uint64(amount + sum(p["amount"] for p in primaries))
self.log.debug("Generating transaction for: %s %s %s", puzzle_hash, amount, repr(coins))
transaction = await self._generate_unsigned_transaction(
amount,
puzzle_hash,
@@ -442,8 +443,7 @@ class Wallet:
negative_change_allowed,
)
assert len(transaction) > 0
self.log.info("About to sign a transaction")
self.log.info("About to sign a transaction: %s", transaction)
await self.hack_populate_secret_keys_for_coin_spends(transaction)
spend_bundle: SpendBundle = await sign_coin_spends(
transaction,
@@ -488,7 +488,7 @@ class Wallet:
await self.wallet_state_manager.wallet_node.update_ui()
# This is to be aggregated together with a CAT offer to ensure that the trade happens
async def create_spend_bundle_relative_chia(self, chia_amount: int, exclude: List[Coin]) -> SpendBundle:
async def create_spend_bundle_relative_chia(self, chia_amount: int, exclude: List[Coin] = []) -> SpendBundle:
list_of_solutions = []
utxos = None
+2
View File
@@ -812,6 +812,8 @@ class WalletNode:
# untrusted peers. For trusted, we always process the state, and we process reorgs as well.
assert self.wallet_state_manager is not None
assert self.server is not None
for coin in request.items:
self.log.info(f"request coin: {coin.coin.name()}{coin}")
async with self.wallet_state_manager.lock:
await self.receive_state_from_peer(
+1
View File
@@ -90,6 +90,7 @@ class WalletPuzzleStore:
try:
sql_records = []
for record in records:
log.debug("Adding derivation record: %s", record)
self.all_puzzle_hashes.add(record.puzzle_hash)
if record.hardened:
hardened = 1
+216 -57
View File
@@ -7,17 +7,18 @@ import time
from collections import defaultdict
from pathlib import Path
from secrets import token_bytes
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple
import aiosqlite
from blspy import G1Element, PrivateKey
from chia.consensus.coinbase import pool_parent_id, farmer_parent_id
from chia.consensus.coinbase import farmer_parent_id, pool_parent_id
from chia.consensus.constants import ConsensusConstants
from chia.pools.pool_puzzles import SINGLETON_LAUNCHER_HASH, solution_to_pool_state
from chia.pools.pool_wallet import PoolWallet
from chia.protocols import wallet_protocol
from chia.protocols.wallet_protocol import PuzzleSolutionResponse, RespondPuzzleSolution, CoinState
from chia.protocols.wallet_protocol import CoinState, PuzzleSolutionResponse, RespondPuzzleSolution
from chia.server.server import ChiaServer
from chia.server.ws_connection import WSChiaConnection
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
@@ -27,16 +28,20 @@ from chia.types.full_block import FullBlock
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.util.byte_types import hexstr_to_bytes
from chia.util.config import process_config_start_method
from chia.util.db_synchronous import db_synchronous_on
from chia.util.db_wrapper import DBWrapper
from chia.util.errors import Err
from chia.util.ints import uint32, uint64, uint128, uint8
from chia.util.db_synchronous import db_synchronous_on
from chia.wallet.cat_wallet.cat_utils import match_cat_puzzle, construct_cat_puzzle
from chia.wallet.cat_wallet.cat_wallet import CATWallet
from chia.util.ints import uint8, uint32, uint64, uint128
from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
from chia.wallet.cat_wallet.cat_utils import construct_cat_puzzle, match_cat_puzzle
from chia.wallet.cat_wallet.cat_wallet import CATWallet
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.did_wallet.did_wallet_puzzles import DID_INNERPUZ_MOD, create_fullpuz, match_did_puzzle
from chia.wallet.key_val_store import KeyValStore
from chia.wallet.nft_wallet.nft_wallet import NFTWallet, NFTWalletInfo
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.puzzle_drivers import PuzzleInfo
from chia.wallet.puzzles.cat_loader import CAT_MOD
@@ -61,8 +66,6 @@ from chia.wallet.wallet_puzzle_store import WalletPuzzleStore
from chia.wallet.wallet_sync_store import WalletSyncStore
from chia.wallet.wallet_transaction_store import WalletTransactionStore
from chia.wallet.wallet_user_store import WalletUserStore
from chia.server.server import ChiaServer
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.wallet_weight_proof_handler import WalletWeightProofHandler
@@ -203,6 +206,12 @@ class WalletStateManager:
self.main_wallet,
wallet_info,
)
elif wallet_info.type == WalletType.NFT:
wallet = await NFTWallet.create(
self,
self.main_wallet,
wallet_info,
)
elif wallet_info.type == WalletType.POOLING_WALLET:
wallet = await PoolWallet.create_from_db(
self,
@@ -238,11 +247,12 @@ class WalletStateManager:
that we can restore the wallet from only the private keys.
"""
targets = list(self.wallets.keys())
self.log.debug("Target wallets to generate puzzle hashes for: %s", repr(targets))
unused: Optional[uint32] = await self.puzzle_store.get_unused_derivation_path()
if unused is None:
# This handles the case where the database has entries but they have all been used
unused = await self.puzzle_store.get_last_derivation_path()
self.log.debug("Tried finding unused: %s", unused)
if unused is None:
# This handles the case where the database is empty
unused = uint32(0)
@@ -357,11 +367,13 @@ class WalletStateManager:
# If we have no unused public keys, we will create new ones
unused: Optional[uint32] = await self.puzzle_store.get_unused_derivation_path()
if unused is None:
self.log.debug("No unused paths, generate more ")
await self.create_more_puzzle_hashes()
# Now we must have unused public keys
unused = await self.puzzle_store.get_unused_derivation_path()
assert unused is not None
# Now we must have unused public keys
unused = await self.puzzle_store.get_unused_derivation_path()
assert unused is not None
self.log.debug("Fetching derivation record for: %s %s %s", unused, wallet_id, hardened)
record: Optional[DerivationRecord] = await self.puzzle_store.get_derivation_record(
unused, wallet_id, hardened
)
@@ -526,7 +538,7 @@ class WalletStateManager:
removals[coin.name()] = coin
return removals
async def fetch_parent_and_check_for_cat(
async def determine_coin_type(
self, peer: WSChiaConnection, coin_state: CoinState, fork_height: Optional[uint32]
) -> Tuple[Optional[uint32], Optional[WalletType]]:
if self.is_pool_reward(coin_state.created_height, coin_state.coin.parent_coin_info) or self.is_farmer_reward(
@@ -542,55 +554,190 @@ class WalletStateManager:
return None, None
parent_coin_state = response[0]
assert parent_coin_state.spent_height == coin_state.created_height
wallet_id = None
wallet_type = None
cs: Optional[CoinSpend] = await self.wallet_node.fetch_puzzle_solution(
coin_spend: Optional[CoinSpend] = await self.wallet_node.fetch_puzzle_solution(
peer, parent_coin_state.spent_height, parent_coin_state.coin
)
if cs is None:
if coin_spend is None:
return None, None
matched, curried_args = match_cat_puzzle(Program.from_bytes(bytes(cs.puzzle_reveal)))
if matched:
mod_hash, tail_hash, inner_puzzle = curried_args
inner_puzzle_hash = inner_puzzle.get_tree_hash()
self.log.info(
f"parent: {parent_coin_state.coin.name()} inner_puzzle_hash for parent is {inner_puzzle_hash}"
)
# Check if the coin is a CAT
cat_matched, cat_curried_args = match_cat_puzzle(Program.from_bytes(bytes(coin_spend.puzzle_reveal)))
if cat_matched:
return await self.handle_cat(cat_curried_args, parent_coin_state, coin_state, coin_spend)
hint_list = compute_coin_hints(cs)
derivation_record = None
for hint in hint_list:
derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(bytes32(hint))
if derivation_record is not None:
break
# Check if the coin is a NFT
# hint
# First spend where 1 mojo coin -> Singleton launcher -> NFT -> NFT
try:
uncurried_nft = UncurriedNFT.uncurry(Program.from_bytes(bytes(coin_spend.puzzle_reveal)))
except Exception:
# This is not a NFT coin, skip NFT handling
pass
else:
return await self.handle_nft(coin_spend, uncurried_nft)
if derivation_record is None:
self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
# Check if the coin is a DID
did_matched, did_curried_args = match_did_puzzle(Program.from_bytes(bytes(coin_spend.puzzle_reveal)))
if did_matched:
return await self.handle_did(did_curried_args, parent_coin_state, coin_state, coin_spend)
return None, None
async def handle_cat(
self,
curried_args: Iterator[Program],
parent_coin_state: CoinState,
coin_state: CoinState,
coin_spend: CoinSpend,
) -> Tuple[Optional[uint32], Optional[WalletType]]:
"""
Handle the new coin when it is a CAT
:param curried_args: Curried arg of the CAT mod
:param parent_coin_state: Parent coin state
:param coin_state: Current coin state
:param coin_spend: New coin spend
:return: Wallet ID & Wallet Type
"""
wallet_id = None
wallet_type = None
mod_hash, tail_hash, inner_puzzle = curried_args
inner_puzzle_hash = inner_puzzle.get_tree_hash()
self.log.info(f"parent: {parent_coin_state.coin.name()} inner_puzzle_hash for parent is {inner_puzzle_hash}")
hint_list = compute_coin_hints(coin_spend)
derivation_record = None
for hint in hint_list:
derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(bytes32(hint))
if derivation_record is not None:
break
if derivation_record is None:
self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
else:
our_inner_puzzle: Program = self.main_wallet.puzzle_for_pk(bytes(derivation_record.pubkey))
asset_id: bytes32 = bytes32(bytes(tail_hash)[1:])
cat_puzzle = construct_cat_puzzle(CAT_MOD, asset_id, our_inner_puzzle)
if cat_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash:
return None, None
if bytes(tail_hash).hex()[2:] in self.default_cats or self.config.get(
"automatically_add_unknown_cats", False
):
cat_wallet = await CATWallet.create_wallet_for_cat(
self, self.main_wallet, bytes(tail_hash).hex()[2:], in_transaction=True
)
wallet_id = cat_wallet.id()
wallet_type = WalletType(cat_wallet.type())
self.state_changed("wallet_created")
else:
our_inner_puzzle: Program = self.main_wallet.puzzle_for_pk(bytes(derivation_record.pubkey))
asset_id: bytes32 = bytes32(bytes(tail_hash)[1:])
cat_puzzle = construct_cat_puzzle(CAT_MOD, asset_id, our_inner_puzzle)
if cat_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash:
return None, None
if bytes(tail_hash).hex()[2:] in self.default_cats or self.config.get(
"automatically_add_unknown_cats", False
):
cat_wallet = await CATWallet.create_wallet_for_cat(
self, self.main_wallet, bytes(tail_hash).hex()[2:], in_transaction=True
)
wallet_id = cat_wallet.id()
wallet_type = WalletType(cat_wallet.type())
self.state_changed("wallet_created")
# Found unacknowledged CAT, save it in the database.
await self.interested_store.add_unacknowledged_token(
asset_id,
CATWallet.default_wallet_name_for_unknown_cat(asset_id.hex()),
parent_coin_state.spent_height,
parent_coin_state.coin.puzzle_hash,
)
self.state_changed("added_stray_cat")
return wallet_id, wallet_type
async def handle_did(
self,
curried_args: Iterator[Program],
parent_coin_state: CoinState,
coin_state: CoinState,
coin_spend: CoinSpend,
) -> Tuple[Optional[uint32], Optional[WalletType]]:
"""
Handle the new coin when it is a DID
:param curried_args: Curried arg of the DID mod
:param parent_coin_state: Parent coin state
:param coin_state: Current coin state
:param coin_spend: New coin spend
:return: Wallet ID & Wallet Type
"""
wallet_id = None
wallet_type = None
p2_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata = curried_args
inner_puzzle_hash = p2_puzzle.get_tree_hash()
self.log.info(f"parent: {parent_coin_state.coin.name()} inner_puzzle_hash for parent is {inner_puzzle_hash}")
hint_list = compute_coin_hints(coin_spend)
derivation_record = None
for hint in hint_list:
derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(bytes32(hint))
if derivation_record is not None:
break
if derivation_record is None:
self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
else:
our_inner_puzzle: Program = self.main_wallet.puzzle_for_pk(bytes(derivation_record.pubkey))
launch_id: bytes32 = bytes32(bytes(singleton_struct.rest().first())[1:])
self.log.info(f"Found DID, launch_id {launch_id}.")
did_puzzle = DID_INNERPUZ_MOD.curry(
our_inner_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata
)
full_puzzle = create_fullpuz(did_puzzle, launch_id)
did_puzzle_empty_recovery = DID_INNERPUZ_MOD.curry(
our_inner_puzzle, Program.to([]).get_tree_hash(), uint64(0), singleton_struct, metadata
)
full_puzzle_empty_recovery = create_fullpuz(did_puzzle_empty_recovery, launch_id)
if full_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash:
if full_puzzle_empty_recovery.get_tree_hash() == coin_state.coin.puzzle_hash:
did_puzzle = did_puzzle_empty_recovery
self.log.info("DID recovery list was reset by the previous owner.")
else:
# Found unacknowledged CAT, save it in the database.
await self.interested_store.add_unacknowledged_token(
asset_id,
CATWallet.default_wallet_name_for_unknown_cat(asset_id.hex()),
parent_coin_state.spent_height,
parent_coin_state.coin.puzzle_hash,
)
self.state_changed("added_stray_cat")
self.log.error("DID puzzle hash doesn't match, please check curried parameters.")
return None, None
# Create DID wallet
response: List[CoinState] = await self.wallet_node.get_coin_state([launch_id])
if len(response) == 0:
self.log.warning(f"Could not find the launch coin with ID: {launch_id}")
return None, None
launch_coin: CoinState = response[0]
did_wallet = await DIDWallet.create_new_did_wallet_from_coin_spend(
self, self.main_wallet, launch_coin.coin, did_puzzle, coin_spend, f"DID {launch_id.hex()}"
)
wallet_id = did_wallet.id()
wallet_type = WalletType(did_wallet.type())
self.state_changed("wallet_created")
return wallet_id, wallet_type
async def handle_nft(
self, coin_spend: CoinSpend, uncurried_nft: UncurriedNFT
) -> Tuple[Optional[uint32], Optional[WalletType]]:
"""
Handle the new coin when it is a NFT
:param coin_spend: New coin spend
:param uncurried_nft: Uncurried NFT
:return: Wallet ID & Wallet Type
"""
wallet_id = None
wallet_type = None
self.log.debug("Handling NFT: %s", coin_spend)
for wallet_info in await self.get_all_wallet_info_entries():
if wallet_info.type == WalletType.NFT:
nft_wallet_info = NFTWalletInfo.from_json_dict(json.loads(wallet_info.data))
self.log.debug(
"Checking NFT wallet %r and inner puzzle %s",
wallet_info.name,
uncurried_nft.inner_puzzle.get_tree_hash(),
)
if not nft_wallet_info.did_wallet_id:
# standard NFT wallet
wallet_id = wallet_info.id
wallet_type = WalletType.NFT
break
if wallet_id is None:
# TODO Modify this for NFT1
self.log.info("Cannot find a NFT wallet, creating a new one.")
nft_wallet: NFTWallet = await NFTWallet.create_new_nft_wallet(
self, self.main_wallet, name="NFT Wallet", in_transaction=True
)
wallet_id = uint32(nft_wallet.wallet_id)
wallet_type = WalletType.NFT
return wallet_id, wallet_type
@@ -638,7 +785,7 @@ class WalletStateManager:
wallet_id = uint32(local_record.wallet_id)
wallet_type = local_record.wallet_type
elif coin_state.created_height is not None:
wallet_id, wallet_type = await self.fetch_parent_and_check_for_cat(peer, coin_state, fork_height)
wallet_id, wallet_type = await self.determine_coin_type(peer, coin_state, fork_height)
if wallet_id is None or wallet_type is None:
self.log.info(f"No wallet for coin state: {coin_state}")
@@ -660,8 +807,10 @@ class WalletStateManager:
# TODO implements this coin got reorged
# TODO: we need to potentially roll back the pool wallet here
pass
# if the new coin has not been spent (i.e not ephemeral)
elif coin_state.created_height is not None and coin_state.spent_height is None:
await self.coin_added(coin_state.coin, coin_state.created_height, all_txs, wallet_id, wallet_type)
# if the coin has been spent
elif coin_state.created_height is not None and coin_state.spent_height is not None:
self.log.info(f"Coin Removed: {coin_state}")
record = await self.coin_store.get_coin_record(coin_state.coin.name())
@@ -822,6 +971,11 @@ class WalletStateManager:
assert len(new_coin_state) == 1
curr_coin_state = new_coin_state[0]
elif record.wallet_type == WalletType.NFT:
if coin_state.spent_height is not None:
nft_wallet = self.wallets[uint32(record.wallet_id)]
await nft_wallet.remove_coin(coin_state.coin, in_transaction=True)
# Check if a child is a singleton launcher
if children is None:
children = await self.wallet_node.fetch_children(peer, coin_state.coin.name(), fork_height)
@@ -934,7 +1088,9 @@ class WalletStateManager:
if existing is not None:
return None
self.log.info(f"Adding coin: {coin} at {height} wallet_id:{wallet_id}")
self.log.info(
f"Adding record to state manager coin: {coin} at {height} wallet_id:{wallet_id} and type: {wallet_type}"
)
farmer_reward = False
pool_reward = False
if self.is_farmer_reward(height, coin.parent_coin_info):
@@ -1019,6 +1175,9 @@ class WalletStateManager:
wallet = self.wallets[wallet_id]
await wallet.coin_added(coin, height)
if wallet_type == WalletType.NFT:
await self.wallets[wallet_id].add_nft_coin(coin, height, in_transaction=True)
await self.create_more_puzzle_hashes(in_transaction=True)
return coin_record_1
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -109,6 +109,7 @@ kwargs = dict(
"chia.wallet.rl_wallet",
"chia.wallet.cat_wallet",
"chia.wallet.did_wallet",
"chia.wallet.nft_wallet",
"chia.wallet.settings",
"chia.wallet.trading",
"chia.wallet.util",
+7
View File
@@ -40,6 +40,13 @@ wallet_program_files = set(
"chia/wallet/puzzles/delegated_tail.clvm",
"chia/wallet/puzzles/settlement_payments.clvm",
"chia/wallet/puzzles/genesis_by_coin_id.clvm",
"chia/wallet/puzzles/singleton_top_layer_v1_1.clvm",
"chia/wallet/puzzles/nft_innerpuz.clvm",
"chia/wallet/puzzles/nft_transfer_program.clvm",
"chia/wallet/puzzles/nft_ownership_transfer_program.clvm",
"chia/wallet/puzzles/nft_metadata_updater.clvm",
"chia/wallet/puzzles/nft_metadata_updater_default.clvm",
"chia/wallet/puzzles/nft_state_layer.clvm",
]
)
+3
View File
@@ -0,0 +1,3 @@
# flake8: noqa: E501
job_timeout = 50
checkout_blocks_and_plots = True
+361 -60
View File
@@ -1,3 +1,6 @@
import json
from typing import Optional
import pytest
from blspy import AugSchemeMPL
@@ -7,10 +10,16 @@ from chia.types.blockchain_format.program import Program
from chia.types.peer_info import PeerInfo
from chia.types.spend_bundle import SpendBundle
from chia.util.ints import uint16, uint32, uint64
from chia.wallet.util.wallet_types import WalletType
from chia.wallet.did_wallet.did_wallet import DIDWallet
from tests.time_out_assert import time_out_assert, time_out_assert_not_none
pytestmark = pytest.mark.skip("TODO: Fix tests")
# pytestmark = pytest.mark.skip("TODO: Fix tests")
async def get_wallet_num(wallet_manager):
return len(await wallet_manager.get_all_wallet_info_entries())
class TestDIDWallet:
@@ -58,6 +67,11 @@ class TestDIDWallet:
wallet_node_0.wallet_state_manager, wallet_0, uint64(101)
)
spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_0.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
@@ -72,6 +86,11 @@ class TestDIDWallet:
wallet_node_1.wallet_state_manager, wallet_1, uint64(201), backup_ids
)
spend_bundle_list = await wallet_node_1.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_1.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
@@ -79,24 +98,29 @@ class TestDIDWallet:
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 201)
await time_out_assert(15, did_wallet_1.get_pending_change_balance, 0)
filename = "test.backup"
did_wallet_1.create_backup(filename)
backup_data = did_wallet_1.create_backup()
# Wallet2 recovers DIDWallet2 to a new set of keys
async with wallet_node_2.wallet_state_manager.lock:
did_wallet_2 = await DIDWallet.create_new_did_wallet_from_recovery(
wallet_node_2.wallet_state_manager, wallet_2, filename
wallet_node_2.wallet_state_manager, wallet_2, backup_data
)
coins = await did_wallet_1.select_coins(1)
coin = coins.copy().pop()
assert did_wallet_2.did_info.temp_coin == coin
newpuzhash = await did_wallet_2.get_new_inner_hash()
newpuzhash = await did_wallet_2.get_new_did_inner_hash()
pubkey = bytes(
(await did_wallet_2.wallet_state_manager.get_unused_derivation_record(did_wallet_2.wallet_info.id)).pubkey
)
message_spend_bundle = await did_wallet_0.create_attestment(
did_wallet_2.did_info.temp_coin.name(), newpuzhash, pubkey, "test.attest"
message_spend_bundle, attest_data = await did_wallet_0.create_attestment(
did_wallet_2.did_info.temp_coin.name(), newpuzhash, pubkey
)
spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_0.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
print(f"pubkey: {pubkey}")
for i in range(1, num_blocks):
@@ -105,10 +129,10 @@ class TestDIDWallet:
(
test_info_list,
test_message_spend_bundle,
) = await did_wallet_2.load_attest_files_for_recovery_spend(["test.attest"])
) = await did_wallet_2.load_attest_files_for_recovery_spend([attest_data])
assert message_spend_bundle == test_message_spend_bundle
await did_wallet_2.recovery_spend(
spend_bundle = await did_wallet_2.recovery_spend(
did_wallet_2.did_info.temp_coin,
newpuzhash,
test_info_list,
@@ -117,6 +141,8 @@ class TestDIDWallet:
)
print(f"pubkey: {did_wallet_2}")
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
@@ -126,6 +152,13 @@ class TestDIDWallet:
some_ph = 32 * b"\2"
await did_wallet_2.create_exit_spend(some_ph)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_2.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
@@ -143,8 +176,8 @@ class TestDIDWallet:
async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_wallet_nodes):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_1 = full_nodes[0]
server_1 = full_node_1.server
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
@@ -156,7 +189,7 @@ class TestDIDWallet:
await server_3.start_client(PeerInfo(self_hostname, uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
@@ -171,10 +204,15 @@ class TestDIDWallet:
did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node.wallet_state_manager, wallet, uint64(101)
)
assert did_wallet.wallet_info.name == "Profile 1"
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph = await wallet2.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101)
@@ -186,8 +224,13 @@ class TestDIDWallet:
wallet_node_2.wallet_state_manager, wallet2, uint64(101), recovery_list
)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet2.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet_2.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_2.get_unconfirmed_balance, 101)
@@ -201,9 +244,14 @@ class TestDIDWallet:
wallet_node_2.wallet_state_manager, wallet2, uint64(201), recovery_list
)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet2.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
assert did_wallet_3.did_info.backup_ids == recovery_list
await time_out_assert(15, did_wallet_3.get_confirmed_balance, 201)
@@ -211,36 +259,52 @@ class TestDIDWallet:
coins = await did_wallet_3.select_coins(1)
coin = coins.pop()
filename = "test.backup"
did_wallet_3.create_backup(filename)
backup_data = did_wallet_3.create_backup()
async with wallet_node.wallet_state_manager.lock:
did_wallet_4 = await DIDWallet.create_new_did_wallet_from_recovery(
wallet_node.wallet_state_manager,
wallet,
filename,
backup_data,
)
assert did_wallet_4.wallet_info.name == "Profile 2"
pubkey = (
await did_wallet_4.wallet_state_manager.get_unused_derivation_record(did_wallet_2.wallet_info.id)
).pubkey
new_ph = await did_wallet_4.get_new_inner_hash()
message_spend_bundle = await did_wallet.create_attestment(coin.name(), new_ph, pubkey, "test1.attest")
message_spend_bundle2 = await did_wallet_2.create_attestment(coin.name(), new_ph, pubkey, "test2.attest")
new_ph = await did_wallet_4.get_new_did_inner_hash()
message_spend_bundle, attest1 = await did_wallet.create_attestment(coin.name(), new_ph, pubkey)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
message_spend_bundle2, attest2 = await did_wallet_2.create_attestment(coin.name(), new_ph, pubkey)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_2.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
message_spend_bundle = message_spend_bundle.aggregate([message_spend_bundle, message_spend_bundle2])
(
test_info_list,
test_message_spend_bundle,
) = await did_wallet_4.load_attest_files_for_recovery_spend(["test1.attest", "test2.attest"])
) = await did_wallet_4.load_attest_files_for_recovery_spend([attest1, attest2])
assert message_spend_bundle == test_message_spend_bundle
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, message_spend_bundle)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_4.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_4.get_confirmed_balance, 201)
await time_out_assert(15, did_wallet_4.get_unconfirmed_balance, 201)
@@ -251,8 +315,8 @@ class TestDIDWallet:
async def test_did_recovery_with_empty_set(self, self_hostname, two_wallet_nodes):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_1 = full_nodes[0]
server_1 = full_node_1.server
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
@@ -263,7 +327,7 @@ class TestDIDWallet:
await server_3.start_client(PeerInfo(self_hostname, uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
@@ -279,27 +343,37 @@ class TestDIDWallet:
wallet_node.wallet_state_manager, wallet, uint64(101)
)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101)
coins = await did_wallet.select_coins(1)
coin = coins.pop()
print(f"Old coin id {coin.name()}")
info = Program.to([])
pubkey = (await did_wallet.wallet_state_manager.get_unused_derivation_record(did_wallet.wallet_info.id)).pubkey
spend_bundle = await did_wallet.recovery_spend(
coin, ph, info, pubkey, SpendBundle([], AugSchemeMPL.aggregate([]))
)
additions = spend_bundle.additions()
assert additions == []
try:
spend_bundle = await did_wallet.recovery_spend(
coin, ph, info, pubkey, SpendBundle([], AugSchemeMPL.aggregate([]))
)
except Exception:
# We expect a CLVM 80 error for this test
pass
else:
assert False
@pytest.mark.asyncio
async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_1 = full_nodes[0]
server_1 = full_node_1.server
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
@@ -309,7 +383,7 @@ class TestDIDWallet:
await server_2.start_client(PeerInfo(self_hostname, uint16(server_1._port)), None)
await server_3.start_client(PeerInfo(self_hostname, uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
@@ -324,10 +398,13 @@ class TestDIDWallet:
did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node.wallet_state_manager, wallet, uint64(101)
)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet2.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101)
@@ -337,11 +414,15 @@ class TestDIDWallet:
did_wallet_2: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node_2.wallet_state_manager, wallet2, uint64(101), recovery_list
)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet2.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet_2.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_2.get_unconfirmed_balance, 101)
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(25, did_wallet_2.get_confirmed_balance, 101)
await time_out_assert(25, did_wallet_2.get_unconfirmed_balance, 101)
assert did_wallet_2.did_info.backup_ids == recovery_list
# Update coin with new ID info
@@ -350,77 +431,297 @@ class TestDIDWallet:
assert did_wallet.did_info.backup_ids == recovery_list
await did_wallet.create_update_spend()
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101)
# DID Wallet 2 recovers into DID Wallet 3 with new innerpuz
filename = "test.backup"
did_wallet_2.create_backup(filename)
backup_data = did_wallet_2.create_backup()
async with wallet_node.wallet_state_manager.lock:
did_wallet_3 = await DIDWallet.create_new_did_wallet_from_recovery(
wallet_node.wallet_state_manager,
wallet,
filename,
backup_data,
)
new_ph = await did_wallet_3.get_new_inner_hash()
new_ph = await did_wallet_3.get_new_did_inner_hash()
coins = await did_wallet_2.select_coins(1)
coin = coins.pop()
pubkey = (
await did_wallet_3.wallet_state_manager.get_unused_derivation_record(did_wallet_3.wallet_info.id)
).pubkey
message_spend_bundle = await did_wallet.create_attestment(coin.name(), new_ph, pubkey, "test.attest")
attest_data = (await did_wallet.create_attestment(coin.name(), new_ph, pubkey))[1]
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
(
info,
message_spend_bundle,
) = await did_wallet_3.load_attest_files_for_recovery_spend(["test.attest"])
) = await did_wallet_3.load_attest_files_for_recovery_spend([attest_data])
await did_wallet_3.recovery_spend(coin, new_ph, info, pubkey, message_spend_bundle)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_3.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet_3.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_3.get_unconfirmed_balance, 101)
# DID Wallet 1 recovery spends into DID Wallet 4
filename = "test.backup"
did_wallet.create_backup(filename)
backup_data = did_wallet.create_backup()
async with wallet_node_2.wallet_state_manager.lock:
did_wallet_4 = await DIDWallet.create_new_did_wallet_from_recovery(
wallet_node_2.wallet_state_manager,
wallet2,
filename,
backup_data,
)
coins = await did_wallet.select_coins(1)
coin = coins.pop()
new_ph = await did_wallet_4.get_new_inner_hash()
new_ph = await did_wallet_4.get_new_did_inner_hash()
pubkey = (
await did_wallet_4.wallet_state_manager.get_unused_derivation_record(did_wallet_4.wallet_info.id)
).pubkey
await did_wallet_3.create_attestment(coin.name(), new_ph, pubkey, "test.attest")
attest1 = (await did_wallet_3.create_attestment(coin.name(), new_ph, pubkey))[1]
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_3.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
(
test_info_list,
test_message_spend_bundle,
) = await did_wallet_4.load_attest_files_for_recovery_spend(["test.attest"])
spend_bundle = await did_wallet_4.recovery_spend(
coin, new_ph, test_info_list, pubkey, test_message_spend_bundle
) = await did_wallet_4.load_attest_files_for_recovery_spend([attest1])
await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, test_message_spend_bundle)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_4.id()
)
await time_out_assert_not_none(15, full_node_1.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet_4.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_4.get_unconfirmed_balance, 101)
await time_out_assert(15, did_wallet.get_confirmed_balance, 0)
await time_out_assert(15, did_wallet.get_unconfirmed_balance, 0)
@pytest.mark.parametrize(
"with_recovery",
[True, False],
)
@pytest.mark.asyncio
async def test_did_transfer(self, two_wallet_nodes, with_recovery):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
wallet2 = wallet_node_2.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
wallet_node.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_2.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
await server_3.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i))
for i in range(1, num_blocks - 1)
]
)
await time_out_assert(15, wallet.get_confirmed_balance, funds)
async with wallet_node.wallet_state_manager.lock:
did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node.wallet_state_manager,
wallet,
uint64(101),
[bytes(ph)],
uint64(1),
{"Twitter": "Test", "GitHub": "测试"},
)
assert did_wallet_1.wallet_info.name == "Profile 1"
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet2.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
# Transfer DID
new_puzhash = await wallet2.get_new_puzzlehash()
await did_wallet_1.transfer_did(new_puzhash, uint64(0), with_recovery)
print(f"Original launch_id {did_wallet_1.did_info.origin_coin.name()}")
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_1.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet2.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
# Check if the DID wallet is created in the wallet2
await time_out_assert(30, len, 2, wallet_node_2.wallet_state_manager.wallets)
# Get the new DID wallet
did_wallets = list(
filter(
lambda w: (w.type == WalletType.DISTRIBUTED_ID),
await wallet_node_2.wallet_state_manager.get_all_wallet_info_entries(),
)
)
did_wallet_2: Optional[DIDWallet] = wallet_node_2.wallet_state_manager.wallets[did_wallets[0].id]
assert did_wallet_1.did_info.origin_coin == did_wallet_2.did_info.origin_coin
if with_recovery:
assert did_wallet_1.did_info.backup_ids[0] == did_wallet_2.did_info.backup_ids[0]
assert did_wallet_1.did_info.num_of_backup_ids_needed == did_wallet_2.did_info.num_of_backup_ids_needed
metadata = json.loads(did_wallet_1.did_info.metadata)
assert metadata["Twitter"] == "Test"
assert metadata["GitHub"] == "测试"
@pytest.mark.asyncio
async def test_update_recovery_list(self, two_wallet_nodes):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
wallet_node.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_2.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
await server_3.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i))
for i in range(1, num_blocks - 1)
]
)
await time_out_assert(15, wallet.get_confirmed_balance, funds)
async with wallet_node.wallet_state_manager.lock:
did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node.wallet_state_manager, wallet, uint64(101), []
)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
await did_wallet_1.update_recovery_list([bytes(ph)], 1)
await did_wallet_1.create_update_spend()
ph2 = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
assert did_wallet_1.did_info.backup_ids[0] == bytes(ph)
assert did_wallet_1.did_info.num_of_backup_ids_needed == 1
@pytest.mark.asyncio
async def test_update_metadata(self, two_wallet_nodes):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
wallet_node.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_2.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
await server_3.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i))
for i in range(1, num_blocks - 1)
]
)
await time_out_assert(15, wallet.get_confirmed_balance, funds)
async with wallet_node.wallet_state_manager.lock:
did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node.wallet_state_manager, wallet, uint64(101), []
)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
metadata = {}
metadata["Twitter"] = "http://www.twitter.com"
await did_wallet_1.update_metadata(metadata)
await did_wallet_1.create_update_spend()
ph2 = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
assert did_wallet_1.did_info.metadata.find("Twitter") > 0
-121
View File
@@ -1,121 +0,0 @@
import logging
import pytest
from chia.rpc.rpc_server import start_rpc_server
from chia.rpc.wallet_rpc_api import WalletRpcApi
from chia.rpc.wallet_rpc_client import WalletRpcClient
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
from chia.types.peer_info import PeerInfo
from chia.util.ints import uint16, uint64
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.util.wallet_types import WalletType
from tests.time_out_assert import time_out_assert
log = logging.getLogger(__name__)
pytestmark = pytest.mark.skip("TODO: Fix tests")
class TestDIDWallet:
@pytest.mark.asyncio
async def test_create_did(self, bt, three_wallet_nodes, self_hostname):
num_blocks = 4
full_nodes, wallets = three_wallet_nodes
full_node_api = full_nodes[0]
full_node_server = full_node_api.server
wallet_node_0, wallet_server_0 = wallets[0]
wallet_node_1, wallet_server_1 = wallets[1]
wallet_node_2, wallet_server_2 = wallets[2]
MAX_WAIT_SECS = 30
wallet = wallet_node_0.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
await wallet_server_0.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
await wallet_server_1.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
await wallet_server_2.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
for i in range(0, num_blocks + 1):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
log.info("Waiting for initial money in Wallet 0 ...")
api_one = WalletRpcApi(wallet_node_0)
config = bt.config
daemon_port = config["daemon_port"]
await wallet_server_0.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
rpc_server_cleanup, test_rpc_port = await start_rpc_server(
api_one,
self_hostname,
daemon_port,
uint16(0),
lambda x: None,
bt.root_path,
config,
connect_to_daemon=False,
)
client = await WalletRpcClient.create(self_hostname, test_rpc_port, bt.root_path, bt.config)
async def got_initial_money():
balances = await client.get_wallet_balance("1")
return balances["confirmed_wallet_balance"] > 0
await time_out_assert(timeout=MAX_WAIT_SECS, function=got_initial_money)
val = await client.create_new_did_wallet(201)
assert isinstance(val, dict)
if "success" in val:
assert val["success"]
assert val["type"] == WalletType.DISTRIBUTED_ID.value
assert val["wallet_id"] > 1
assert len(val["my_did"]) == 64
assert bytes.fromhex(val["my_did"])
main_wallet_2 = wallet_node_2.wallet_state_manager.main_wallet
ph2 = await main_wallet_2.get_new_puzzlehash()
for i in range(0, num_blocks + 1):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
recovery_list = [bytes.fromhex(val["my_did"])]
async with wallet_node_2.wallet_state_manager.lock:
did_wallet_2: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node_2.wallet_state_manager, main_wallet_2, uint64(101), recovery_list
)
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
filename = "test.backup"
did_wallet_2.create_backup(filename)
val = await client.create_new_did_wallet_from_recovery(filename)
if "success" in val:
assert val["success"]
assert val["type"] == WalletType.DISTRIBUTED_ID.value
assert val["wallet_id"] > 1
did_wallet_id_3 = val["wallet_id"]
assert len(val["my_did"]) == 64
assert bytes.fromhex(val["my_did"]) == did_wallet_2.did_info.origin_coin.name()
assert bytes.fromhex(val["coin_name"])
assert bytes.fromhex(val["newpuzhash"])
assert bytes.fromhex(val["pubkey"])
filename = "test.attest"
val = await client.did_create_attest(
did_wallet_2.wallet_id, val["coin_name"], val["pubkey"], val["newpuzhash"], filename
)
if "success" in val:
assert val["success"]
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
val = await client.did_recovery_spend(did_wallet_id_3, [filename])
if "success" in val:
assert val["success"]
for i in range(0, num_blocks * 2):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
val = await client.get_wallet_balance(did_wallet_id_3)
assert val["confirmed_wallet_balance"] == 101
await rpc_server_cleanup()
View File
+1
View File
@@ -0,0 +1 @@
checkout_blocks_and_plots = True
+93
View File
@@ -0,0 +1,93 @@
from chia.types.blockchain_format.program import INFINITE_COST, Program
from chia.wallet.puzzles.load_clvm import load_clvm
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_for_pk, solution_for_conditions
from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition
from tests.core.make_block_generator import int_to_public_key
SINGLETON_MOD = load_clvm("singleton_top_layer.clvm")
LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
DID_MOD = load_clvm("did_innerpuz.clvm")
NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm")
STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm")
LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash()
NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
SINGLETON_MOD_HASH = SINGLETON_MOD.get_tree_hash()
LAUNCHER_ID = Program.to("launcher-id").get_tree_hash()
NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.clvm")
def test_new_nft_ownership_layer() -> None:
pubkey = int_to_public_key(1)
innerpuz = puzzle_for_pk(pubkey)
my_amount = 1
destination: Program = puzzle_for_pk(int_to_public_key(2))
condition_list = [make_create_coin_condition(destination.get_tree_hash(), my_amount, [])]
metadata = [
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
]
solution = Program.to(
[
NFT_STATE_LAYER_MOD_HASH,
metadata,
NFT_METADATA_UPDATER.get_tree_hash(),
innerpuz,
# below here is the solution
solution_for_conditions(condition_list),
my_amount,
0,
]
)
cost, res = NFT_STATE_LAYER_MOD.run_with_cost(INFINITE_COST, solution)
assert res.first().first().as_int() == 73
assert res.first().rest().first().as_int() == 1
assert res.rest().rest().first().first().as_int() == 51
assert (
res.rest().rest().first().rest().first().as_atom()
== NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination
).get_tree_hash()
)
def test_update_metadata() -> None:
pubkey = int_to_public_key(1)
innerpuz = puzzle_for_pk(pubkey)
my_amount = 1
destination: Program = puzzle_for_pk(int_to_public_key(2))
condition_list = [make_create_coin_condition(destination.get_tree_hash(), my_amount, [])]
condition_list.append([-24, NFT_METADATA_UPDATER, "https://www.chia.net/img/branding/chia-logo-2.svg"])
metadata = [
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
]
solution = Program.to(
[
NFT_STATE_LAYER_MOD_HASH,
metadata,
NFT_METADATA_UPDATER.get_tree_hash(),
innerpuz,
# below here is the solution
solution_for_conditions(condition_list),
my_amount,
0,
]
)
metadata = [
("u", ["https://www.chia.net/img/branding/chia-logo-2.svg", "https://www.chia.net/img/branding/chia-logo.svg"]),
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
]
cost, res = NFT_STATE_LAYER_MOD.run_with_cost(INFINITE_COST, solution)
assert res.first().first().as_int() == 73
assert res.first().rest().first().as_int() == 1
assert res.rest().rest().first().first().as_int() == 51
assert (
res.rest().rest().first().rest().first().as_atom()
== NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination
).get_tree_hash()
)
+468
View File
@@ -0,0 +1,468 @@
import asyncio
# pytestmark = pytest.mark.skip("TODO: Fix tests")
from typing import Any
import pytest
from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
from chia.full_node.mempool_manager import MempoolManager
from chia.rpc.wallet_rpc_api import WalletRpcApi
from chia.simulator.full_node_simulator import FullNodeSimulator
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.peer_info import PeerInfo
from chia.util.ints import uint16, uint32
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.util.compute_memos import compute_memos
from chia.wallet.util.wallet_types import WalletType
from tests.time_out_assert import time_out_assert, time_out_assert_not_none
async def tx_in_pool(mempool: MempoolManager, tx_id: bytes32) -> bool:
tx = mempool.get_spendbundle(tx_id)
if tx is None:
return False
return True
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_wallet_creation_automatically(two_wallet_nodes: Any, trusted: Any) -> None:
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
full_node_server = full_node_api.server
wallet_node_0, server_0 = wallets[0]
wallet_node_1, server_1 = wallets[1]
wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
ph = await wallet_0.get_new_puzzlehash()
ph1 = await wallet_1.get_new_puzzlehash()
if trusted:
wallet_node_0.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_1.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
else:
wallet_node_0.config["trusted_peers"] = {}
wallet_node_1.config["trusted_peers"] = {}
await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1)]
)
await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds)
await time_out_assert(10, wallet_0.get_confirmed_balance, funds)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, wallet_0.get_pending_change_balance, 0)
nft_wallet_0 = await NFTWallet.create_new_nft_wallet(
wallet_node_0.wallet_state_manager, wallet_0, name="NFT WALLET 1"
)
metadata = Program.to(
[
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F185"),
]
)
sb = await nft_wallet_0.generate_new_nft(metadata)
assert sb
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
await time_out_assert(5, len, 1, nft_wallet_0.nft_wallet_info.my_nft_coins)
coins = nft_wallet_0.nft_wallet_info.my_nft_coins
assert len(coins) == 1, "nft not generated"
sb = await nft_wallet_0.transfer_nft(coins[0], ph1)
assert sb is not None
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
assert len(wallet_node_1.wallet_state_manager.wallets) == 2
# Get the new NFT wallet
nft_wallets = await wallet_node_1.wallet_state_manager.get_all_wallet_info_entries(WalletType.NFT)
assert len(nft_wallets) == 1
nft_wallet_1: NFTWallet = wallet_node_1.wallet_state_manager.wallets[nft_wallets[0].id]
await time_out_assert(5, len, 1, nft_wallet_1.nft_wallet_info.my_nft_coins)
coins = nft_wallet_0.nft_wallet_info.my_nft_coins
assert len(coins) == 0
coins = nft_wallet_1.nft_wallet_info.my_nft_coins
assert len(coins) == 1
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_wallet_creation_and_transfer(two_wallet_nodes: Any, trusted: Any) -> None:
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api: FullNodeSimulator = full_nodes[0]
full_node_server = full_node_api.server
wallet_node_0, server_0 = wallets[0]
wallet_node_1, server_1 = wallets[1]
wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
ph = await wallet_0.get_new_puzzlehash()
ph1 = await wallet_1.get_new_puzzlehash()
if trusted:
wallet_node_0.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_1.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
else:
wallet_node_0.config["trusted_peers"] = {}
wallet_node_1.config["trusted_peers"] = {}
await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1)]
)
await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds)
await time_out_assert(10, wallet_0.get_confirmed_balance, funds)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, wallet_0.get_pending_change_balance, 0)
nft_wallet_0 = await NFTWallet.create_new_nft_wallet(
wallet_node_0.wallet_state_manager, wallet_0, name="NFT WALLET 1"
)
metadata = Program.to(
[
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F185"),
]
)
sb = await nft_wallet_0.generate_new_nft(metadata)
assert sb
# ensure hints are generated
assert compute_memos(sb)
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
coins = nft_wallet_0.nft_wallet_info.my_nft_coins
assert len(coins) == 1, "nft not generated"
metadata = Program.to(
[
("u", ["https://www.test.net/logo.svg"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F181"),
]
)
sb = await nft_wallet_0.generate_new_nft(metadata)
assert sb
# ensure hints are generated
assert compute_memos(sb)
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
await time_out_assert(15, len, 2, nft_wallet_0.nft_wallet_info.my_nft_coins)
coins = nft_wallet_0.nft_wallet_info.my_nft_coins
assert len(coins) == 2, "nft not generated"
nft_wallet_1 = await NFTWallet.create_new_nft_wallet(
wallet_node_1.wallet_state_manager, wallet_1, name="NFT WALLET 2"
)
sb = await nft_wallet_0.transfer_nft(coins[1], ph1)
assert sb is not None
# ensure hints are generated
assert compute_memos(sb)
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
coins = nft_wallet_0.nft_wallet_info.my_nft_coins
assert len(coins) == 1
coins = nft_wallet_1.nft_wallet_info.my_nft_coins
assert len(coins) == 1
# Send it back to original owner
nsb = await nft_wallet_1.transfer_nft(coins[0], ph)
assert nsb is not None
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, nsb.name())
# ensure hints are generated
assert compute_memos(nsb)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
await time_out_assert(5, len, 2, nft_wallet_0.nft_wallet_info.my_nft_coins)
await time_out_assert(5, len, 0, nft_wallet_1.nft_wallet_info.my_nft_coins)
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_wallet_rpc_creation_and_list(two_wallet_nodes: Any, trusted: Any) -> None:
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
full_node_server = full_node_api.server
wallet_node_0, server_0 = wallets[0]
wallet_node_1, server_1 = wallets[1]
wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
ph = await wallet_0.get_new_puzzlehash()
_ = await wallet_1.get_new_puzzlehash()
if trusted:
wallet_node_0.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_1.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
else:
wallet_node_0.config["trusted_peers"] = {}
wallet_node_1.config["trusted_peers"] = {}
await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
api_0 = WalletRpcApi(wallet_node_0)
nft_wallet_0 = await api_0.create_new_wallet(dict(wallet_type="nft_wallet", name="NFT WALLET 1"))
assert isinstance(nft_wallet_0, dict)
assert nft_wallet_0.get("success")
nft_wallet_0_id = nft_wallet_0["wallet_id"]
tr1 = await api_0.nft_mint_nft(
{
"wallet_id": nft_wallet_0_id,
"artist_address": ph,
"hash": "0xD4584AD463139FA8C0D9F68F4B59F185",
"uris": ["https://www.chia.net/img/branding/chia-logo.svg"],
}
)
assert isinstance(tr1, dict)
assert tr1.get("success")
sb = tr1["spend_bundle"]
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
tr2 = await api_0.nft_mint_nft(
{
"wallet_id": nft_wallet_0_id,
"artist_address": ph,
"hash": "0xD4584AD463139FA8C0D9F68F4B59F184",
"uris": ["https://chialisp.com/img/logo.svg"],
}
)
assert isinstance(tr2, dict)
assert tr2.get("success")
sb = tr2["spend_bundle"]
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
time_left = 5.0
while time_left > 0:
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
try:
assert isinstance(coins_response, dict)
assert coins_response.get("success")
coins = coins_response["nft_list"]
assert len(coins) == 2
uris = []
for coin in coins:
uris.append(coin.to_json_dict()["data_uris"][0])
assert len(uris) == 2
assert "https://chialisp.com/img/logo.svg" in uris
assert bytes32.fromhex(coins[1].to_json_dict()["nft_coin_id"]) in [x.name() for x in sb.additions()]
except AssertionError:
if time_left < 0:
raise
await asyncio.sleep(0.5)
time_left -= 0.5
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_wallet_rpc_update_metadata(two_wallet_nodes: Any, trusted: Any) -> None:
num_blocks = 3
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
full_node_server = full_node_api.server
wallet_node_0, server_0 = wallets[0]
wallet_node_1, server_1 = wallets[1]
wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
ph = await wallet_0.get_new_puzzlehash()
_ = await wallet_1.get_new_puzzlehash()
if trusted:
wallet_node_0.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_1.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
else:
wallet_node_0.config["trusted_peers"] = {}
wallet_node_1.config["trusted_peers"] = {}
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
api_0 = WalletRpcApi(wallet_node_0)
await time_out_assert(10, wallet_node_0.wallet_state_manager.synced, True)
await time_out_assert(10, wallet_node_1.wallet_state_manager.synced, True)
nft_wallet_0 = await api_0.create_new_wallet(dict(wallet_type="nft_wallet", name="NFT WALLET 1"))
assert isinstance(nft_wallet_0, dict)
assert nft_wallet_0.get("success")
nft_wallet_0_id = nft_wallet_0["wallet_id"]
# mint NFT
resp = await api_0.nft_mint_nft(
{
"wallet_id": nft_wallet_0_id,
"artist_address": ph,
"hash": "0xD4584AD463139FA8C0D9F68F4B59F185",
"uris": ["https://www.chia.net/img/branding/chia-logo.svg"],
}
)
assert resp.get("success")
sb = resp["spend_bundle"]
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
time_left = 5.0
coins_response = {}
while time_left > 0:
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
if coins_response.get("nft_list"):
break
await asyncio.sleep(0.5)
time_left -= 0.5
assert coins_response["nft_list"], isinstance(coins_response, dict)
assert coins_response.get("success")
coins = coins_response["nft_list"]
coin = coins[0].to_json_dict()
nft_coin_id = coin["nft_coin_id"]
# add another URI
tr1 = await api_0.nft_add_uri(
{
"wallet_id": nft_wallet_0_id,
"nft_coin_id": nft_coin_id,
"hash": "0xD4584AD463139FA8C0D9F68F4B59F185",
"uri": "https://www.chia.net/img/branding/chia-logo-white.svg",
}
)
assert isinstance(tr1, dict)
assert tr1.get("success")
sb = tr1["spend_bundle"]
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
# check that new URI was added
time_left = 5.0
while time_left > 0:
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
try:
assert isinstance(coins_response, dict)
assert coins_response.get("success")
coins = coins_response["nft_list"]
assert len(coins) == 1
coin = coins[0].to_json_dict()
uris = coin["data_uris"]
assert len(uris) == 2
assert "https://www.chia.net/img/branding/chia-logo-white.svg" in uris
except AssertionError:
if time_left < 0:
raise
await asyncio.sleep(0.5)
time_left -= 0.5
# add yet another URI
nft_coin_id = coin["nft_coin_id"]
tr1 = await api_0.nft_add_uri(
{
"wallet_id": nft_wallet_0_id,
"nft_coin_id": nft_coin_id,
"hash": "0xD4584AD463139FA8C0D9F68F4B59F185",
"uri": "https://www.chia.net/img/branding/chia-logo-more-white.svg",
}
)
assert isinstance(tr1, dict)
assert tr1.get("success")
sb = tr1["spend_bundle"]
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
time_left = 5.0
while time_left > 0:
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
try:
assert isinstance(coins_response, dict)
assert coins_response.get("success")
coins = coins_response["nft_list"]
assert len(coins) == 1
coin = coins[0].to_json_dict()
uris = coin["data_uris"]
assert len(uris) == 3
assert "https://www.chia.net/img/branding/chia-logo-more-white.svg" in uris
except AssertionError:
if time_left < 0:
raise
await asyncio.sleep(0.5)
time_left -= 0.5
+92
View File
@@ -1,4 +1,5 @@
import dataclasses
import json
import logging
from operator import attrgetter
from typing import Any, Dict, List, Optional, Tuple
@@ -31,6 +32,7 @@ from chia.util.ints import uint16, uint32, uint64
from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
from chia.wallet.cat_wallet.cat_wallet import CATWallet
from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.trading.trade_status import TradeStatus
from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.transaction_sorting import SortKey
@@ -730,6 +732,96 @@ async def test_offer_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment)
assert len(all_offers) == 2
@pytest.mark.asyncio
async def test_did_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
env: WalletRpcTestEnvironment = wallet_rpc_environment
wallet_1: Wallet = env.wallet_1.wallet
wallet_2: Wallet = env.wallet_2.wallet
wallet_1_node: WalletNode = env.wallet_1.node
wallet_2_node: WalletNode = env.wallet_2.node
wallet_1_rpc: WalletRpcClient = env.wallet_1.rpc_client
full_node_api: FullNodeSimulator = env.full_node.api
await generate_funds(env.full_node.api, env.wallet_1, 5)
# Create a DID wallet
res = await wallet_1_rpc.create_new_did_wallet(amount=1, name=None)
assert res["success"]
did_wallet_id_0 = res["wallet_id"]
did_id_0 = res["my_did"]
# Get wallet name
res = await wallet_1_rpc.did_get_wallet_name(did_wallet_id_0)
assert res["success"]
assert res["name"] == "Profile 1"
# Set wallet name
new_wallet_name = "test name"
res = await wallet_1_rpc.did_set_wallet_name(did_wallet_id_0, new_wallet_name)
assert res["success"]
res = await wallet_1_rpc.did_get_wallet_name(did_wallet_id_0)
assert res["success"]
assert res["name"] == new_wallet_name
with pytest.raises(ValueError, match="Wallet id 1 is not a DID wallet"):
await wallet_1_rpc.did_set_wallet_name(wallet_1.id(), new_wallet_name)
# Check DID ID
res = await wallet_1_rpc.get_did_id(did_wallet_id_0)
assert res["success"]
assert did_id_0 == res["my_did"]
# Create backup file
res = await wallet_1_rpc.create_did_backup_file(did_wallet_id_0, "backup.did")
assert res["success"]
for _ in range(3):
await farm_transaction_block(full_node_api, wallet_1_node)
# Update recovery list
res = await wallet_1_rpc.update_did_recovery_list(did_wallet_id_0, [did_id_0], 1)
assert res["success"]
res = await wallet_1_rpc.get_did_recovery_list(did_wallet_id_0)
assert res["num_required"] == 1
assert res["recovery_list"][0] == did_id_0
for _ in range(3):
await farm_transaction_block(full_node_api, wallet_1_node)
# Update metadata
res = await wallet_1_rpc.update_did_metadata(did_wallet_id_0, {"Twitter": "Https://test"})
assert res["success"]
for _ in range(3):
await farm_transaction_block(full_node_api, wallet_1_node)
res = await wallet_1_rpc.get_did_metadata(did_wallet_id_0)
assert res["metadata"]["Twitter"] == "Https://test"
for _ in range(3):
await farm_transaction_block(full_node_api, wallet_1_node)
# Transfer DID
addr = encode_puzzle_hash(await wallet_2.get_new_puzzlehash(), "txch")
res = await wallet_1_rpc.did_transfer_did(did_wallet_id_0, addr, 0, True)
assert res["success"]
for _ in range(3):
await farm_transaction_block(full_node_api, wallet_1_node)
assert wallet_2_node.wallet_state_manager is not None
did_wallets = list(
filter(
lambda w: (w.type == WalletType.DISTRIBUTED_ID),
await wallet_2_node.wallet_state_manager.get_all_wallet_info_entries(),
)
)
did_wallet_2: DIDWallet = wallet_2_node.wallet_state_manager.wallets[did_wallets[0].id]
assert did_wallet_2.get_my_DID() == did_id_0
metadata = json.loads(did_wallet_2.did_info.metadata)
assert metadata["Twitter"] == "Https://test"
@pytest.mark.asyncio
async def test_key_and_address_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
env: WalletRpcTestEnvironment = wallet_rpc_environment