Merge pull request #12267 from Chia-Network/catchup/long_lived_atari_from_long_lived_datalayer_c438d08bc132cad349f03d3868d7cb386813d692

catchup: into long_lived/atari from long_lived/datalayer @ c438d08bc1
This commit is contained in:
wjblanke
2022-07-06 16:54:50 -07:00
committed by GitHub
100 changed files with 7647 additions and 1491 deletions
@@ -95,7 +95,7 @@ jobs:
- name: Test wallet-cat_wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/cat_wallet/test_cat_lifecycle.py tests/wallet/cat_wallet/test_cat_wallet.py tests/wallet/cat_wallet/test_offer_lifecycle.py tests/wallet/cat_wallet/test_trades.py
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/cat_wallet/test_cat_lifecycle.py tests/wallet/cat_wallet/test_cat_outer_puzzle.py tests/wallet/cat_wallet/test_cat_wallet.py tests/wallet/cat_wallet/test_offer_lifecycle.py tests/wallet/cat_wallet/test_trades.py
- name: Process coverage data
run: |
@@ -95,7 +95,7 @@ jobs:
- 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
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_1_offers.py tests/wallet/nft_wallet/test_nft_lifecycle.py tests/wallet/nft_wallet/test_nft_offers.py tests/wallet/nft_wallet/test_nft_puzzles.py tests/wallet/nft_wallet/test_nft_wallet.py tests/wallet/nft_wallet/test_ownership_outer_puzzle.py
- name: Process coverage data
run: |
@@ -95,7 +95,7 @@ jobs:
- name: Test wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" tests/wallet/test_bech32m.py tests/wallet/test_chialisp.py tests/wallet/test_coin_selection.py tests/wallet/test_puzzle_store.py tests/wallet/test_singleton.py tests/wallet/test_singleton_lifecycle.py tests/wallet/test_singleton_lifecycle_fast.py tests/wallet/test_taproot.py tests/wallet/test_wallet.py tests/wallet/test_wallet_blockchain.py tests/wallet/test_wallet_interested_store.py tests/wallet/test_wallet_key_val_store.py tests/wallet/test_wallet_retry.py tests/wallet/test_wallet_store.py tests/wallet/test_wallet_user_store.py
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" tests/wallet/test_bech32m.py tests/wallet/test_chialisp.py tests/wallet/test_coin_selection.py tests/wallet/test_nft_store.py tests/wallet/test_puzzle_store.py tests/wallet/test_singleton.py tests/wallet/test_singleton_lifecycle.py tests/wallet/test_singleton_lifecycle_fast.py tests/wallet/test_taproot.py tests/wallet/test_wallet.py tests/wallet/test_wallet_blockchain.py tests/wallet/test_wallet_interested_store.py tests/wallet/test_wallet_key_val_store.py tests/wallet/test_wallet_retry.py tests/wallet/test_wallet_store.py tests/wallet/test_wallet_user_store.py
- name: Process coverage data
run: |
@@ -94,7 +94,7 @@ jobs:
- name: Test wallet-cat_wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/cat_wallet/test_cat_lifecycle.py tests/wallet/cat_wallet/test_cat_wallet.py tests/wallet/cat_wallet/test_offer_lifecycle.py tests/wallet/cat_wallet/test_trades.py
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/cat_wallet/test_cat_lifecycle.py tests/wallet/cat_wallet/test_cat_outer_puzzle.py tests/wallet/cat_wallet/test_cat_wallet.py tests/wallet/cat_wallet/test_offer_lifecycle.py tests/wallet/cat_wallet/test_trades.py
- name: Process coverage data
run: |
@@ -94,7 +94,7 @@ jobs:
- 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
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_1_offers.py tests/wallet/nft_wallet/test_nft_lifecycle.py tests/wallet/nft_wallet/test_nft_offers.py tests/wallet/nft_wallet/test_nft_puzzles.py tests/wallet/nft_wallet/test_nft_wallet.py tests/wallet/nft_wallet/test_ownership_outer_puzzle.py
- name: Process coverage data
run: |
@@ -94,7 +94,7 @@ jobs:
- name: Test wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" tests/wallet/test_bech32m.py tests/wallet/test_chialisp.py tests/wallet/test_coin_selection.py tests/wallet/test_puzzle_store.py tests/wallet/test_singleton.py tests/wallet/test_singleton_lifecycle.py tests/wallet/test_singleton_lifecycle_fast.py tests/wallet/test_taproot.py tests/wallet/test_wallet.py tests/wallet/test_wallet_blockchain.py tests/wallet/test_wallet_interested_store.py tests/wallet/test_wallet_key_val_store.py tests/wallet/test_wallet_retry.py tests/wallet/test_wallet_store.py tests/wallet/test_wallet_user_store.py
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" tests/wallet/test_bech32m.py tests/wallet/test_chialisp.py tests/wallet/test_coin_selection.py tests/wallet/test_nft_store.py tests/wallet/test_puzzle_store.py tests/wallet/test_singleton.py tests/wallet/test_singleton_lifecycle.py tests/wallet/test_singleton_lifecycle_fast.py tests/wallet/test_taproot.py tests/wallet/test_wallet.py tests/wallet/test_wallet_blockchain.py tests/wallet/test_wallet_interested_store.py tests/wallet/test_wallet_key_val_store.py tests/wallet/test_wallet_retry.py tests/wallet/test_wallet_store.py tests/wallet/test_wallet_user_store.py
- name: Process coverage data
run: |
+3
View File
@@ -19,4 +19,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
- name: Run install script
run: |
sh install.sh -d
- uses: pre-commit/action@v2.0.3
@@ -197,16 +197,6 @@ 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: |
+6 -8
View File
@@ -59,12 +59,10 @@ repos:
rev: 3.9.2
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.950
- repo: local
hooks:
- id: mypy
additional_dependencies: [filelock, pytest, pytest-asyncio, types-aiofiles, types-click, types-setuptools, types-PyYAML]
# This intentionally counters the settings in mypy.ini to allow a loose local
# check and a strict CI check. This difference may or may not be retained long
# term.
args: [--no-warn-unused-ignores]
- id: mypy
name: mypy
entry: ./activated.py mypy
language: python
pass_filenames: false
+86
View File
@@ -10,6 +10,92 @@ for setuptools_scm/PEP 440 reasons.
### What's Changed
## 1.4.0 Chia blockchain 2022-6-29
### Added
- Added support for NFTs!!! :party:
- Added `chia wallet nft` command (see https://docs.chia.net/docs/13cli/did_cli)
- Added `chia wallet did` command (see https://docs.chia.net/docs/12rpcs/nft_rpcs)
- Added RPCs for DID (see https://docs.chia.net/docs/12rpcs/did_rpcs)
- Added RPCs for NFT (see https://docs.chia.net/docs/12rpcs/nft_rpcs)
- Enable stricter mempool rule when dealing with multiple extra arguments
- Added a retry when loading pool info from a pool at 2 minute intervals
- Added CLI options `--sort-by-height` and sort-by-relevance` to `chia wallet get_transactions`
- Harvester: Introduce `recursive_plot_scan`
- Add libgmp-dev to Bladebit installation - thanks to @TheLastCicada
- Add support for multiple of the same CAT in aggregate offers - Thanks to @roseiliend
### Changed
- New coin selection algorithm based on bitcoin knapsack. Previously chia selected the largest coin
- Updated chiapos to 1.0.10
- Updated chiavdf to 1.0.6
- Updated blspy to 1.0.13
- Updated setproctitle to 1.2.3
- Updated PyYAML to 6.0
- Updated pyinstaller to 5.0
- Bump clvm_tools_rs version to 0.1.9 for clvm stepper and add a test
- Modest speedup of syncing by batching coin lookups
- Cmds: Use the new `plot_count` of `get_pool_state` in `plotnft show`
- Set mempool size back to the original size at launch
- Plotting|tests|setup: Improve `PlotManager` cache
- Wallet: Drop unused `WalletStateManager.get_derivation_index`
- Harvester: Tweak `get_plots` RPC
- Remove explicit multidict version from setup.py
- Simplify install.sh ubuntu version tracking
- Optimize BLS verification when public key is repeated
- Use Install.ps1 in build_windows.ps1
- Updated warning about `CHIA_ROOT` being set when running init
- Cmds: Adjust stop daemon output
- Remove unused functions on MerkleSet
- Optimize `hash_coin_list()`
- Update CONTRIBUTING.md
- Remove outdated 3.8 upgrade comment
- Hint refactor
- Replace MerkleSet with the rust implementation
- Simplify SizedBytes and StructStream
- Allow services to set a non-default max request body size limit
- Reduce the redundant computations of coin_ids in block_body_validation
- Uses the new `from_bytes_unchecked` method in blspy, to improve perfo…
- Remove the cache from CoinStore
- Keep daemon websocket alive during keyring unlock
- Support searching derived addresses on testnet.
- Optimize code to not perform useless subgroup checks
- Restore missing hints being stored as None (instead of 0-length bytes)
- Coin simplification
- Harvester: Use a set instead of a list to speed up availability checks
- Improved performance of debug log output
- Update plotters installation to include an `apt update` - thanks to @TheLastCicada
- Early return from `_set_spent function` - Thanks @neurosis69
- Remove redundant condition in `get_coin_records` - Thanks @neurosis69
- Write python version error to stderr - thanks to @LuaKT
### Fixed
- Fixed issues with harvesters not reconnecting properly - fixes #11466
- Return not synced if there are no connections - fixes #12090
- Fix issues with wallet resending transactions on various mempool and node errors - fixes #10873
- Fix some issues with `plotnft show` (#11897)
- Handle ephemeral ports and dual stack (ipv4 & ipv6)
- Fix issues when wallet syncing and rolling back too far in the past
- Fixes issues with the Farmer Reward dialog incorrectly reporting there is no private key (#11036)
- Fix race condition, blockchain can change between two calls to get_peak
- Wallet: Fix `CATLineageStore` creation in `create_new_cat_wallet`
- Fix incorrect return in "rollback_to_block"
- Wallet: Some rollback fixes
- Fix issue with missing coins
- Fix Newer block issue
- Fix jsonify bool
- Fix wallet introducers for testnet
- Correct wallet CLI sent/received indication
- Correct "Older block not found" error message
- Print MempoolInclusionStatus as string
- Optimize Program.curry()
- Improve detection of disconnected websocket between services
- Correct install.sh usage short options list
- Make sure we set the sync to height correctly when we roll back
## 1.3.5 Chia blockchain 2022-5-11
### Added
+6
View File
@@ -0,0 +1,6 @@
$ErrorActionPreference = "Stop"
$script_directory = Split-Path $MyInvocation.MyCommand.Path -Parent
& "$script_directory/venv/Scripts/Activate.ps1"
& "$args"
Executable
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
import os
import pathlib
import subprocess
import sys
here = pathlib.Path(__file__).parent
def main(*args: str) -> int:
if len(args) == 0:
print("Parameters required")
return 1
if sys.platform == "win32":
script = "activated.ps1"
command = ["powershell", os.fspath(here.joinpath(script)), *args]
else:
script = "activated.sh"
command = [os.fspath(here.joinpath(script)), *args]
completed_process = subprocess.run(command)
return completed_process.returncode
sys.exit(main(*sys.argv[1:]))
Executable
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -o errexit
SCRIPT_DIRECTORY=$(cd -- "$(dirname -- "$0")"; pwd)
# shellcheck disable=SC1091
source "${SCRIPT_DIRECTORY}/venv/bin/activate"
"$@"
+3
View File
@@ -117,6 +117,7 @@ def configure(
config["introducer"]["port"] = int(testnet_port)
config["full_node"]["introducer_peer"]["host"] = testnet_introducer
config["full_node"]["dns_servers"] = [testnet_dns_introducer]
config["wallet"]["introducer_peer"]["host"] = testnet_introducer
config["wallet"]["dns_servers"] = [testnet_dns_introducer]
config["selected_network"] = testnet
config["harvester"]["selected_network"] = testnet
@@ -154,6 +155,8 @@ def configure(
config["introducer"]["port"] = int(mainnet_port)
config["full_node"]["introducer_peer"]["host"] = mainnet_introducer
config["full_node"]["dns_servers"] = [mainnet_dns_introducer]
config["wallet"]["introducer_peer"]["host"] = mainnet_introducer
config["wallet"]["dns_servers"] = [mainnet_dns_introducer]
config["selected_network"] = net
config["harvester"]["selected_network"] = net
config["pool"]["selected_network"] = net
+16 -17
View File
@@ -8,7 +8,7 @@ import json
import time
from pprint import pprint
from typing import List, Dict, Optional, Callable
from typing import Any, List, Dict, Optional, Callable
from chia.cmds.units import units
from chia.cmds.wallet_funcs import print_balance, wallet_coin_unit
@@ -114,7 +114,7 @@ async def pprint_pool_wallet_state(
wallet_id: int,
pool_wallet_info: PoolWalletInfo,
address_prefix: str,
pool_state_dict: Dict,
pool_state_dict: Optional[Dict[str, Any]],
):
if pool_wallet_info.current.state == PoolSingletonState.LEAVING_POOL and pool_wallet_info.target is None:
expected_leave_height = pool_wallet_info.singleton_block_height + pool_wallet_info.current.relative_lock_height
@@ -127,7 +127,7 @@ async def pprint_pool_wallet_state(
"Target address (not for plotting): "
f"{encode_puzzle_hash(pool_wallet_info.current.target_puzzle_hash, address_prefix)}"
)
print(f"Number of plots: {pool_state_dict[pool_wallet_info.launcher_id]['plot_count']}")
print(f"Number of plots: {0 if pool_state_dict is None else pool_state_dict['plot_count']}")
print(f"Owner public key: {pool_wallet_info.current.owner_pubkey}")
print(
@@ -145,12 +145,11 @@ async def pprint_pool_wallet_state(
print(f"Claimable balance: {print_balance(balance, scale, address_prefix)}")
if pool_wallet_info.current.state == PoolSingletonState.FARMING_TO_POOL:
print(f"Current pool URL: {pool_wallet_info.current.pool_url}")
if pool_wallet_info.launcher_id in pool_state_dict:
pool_state = pool_state_dict[pool_wallet_info.launcher_id]
print(f"Current difficulty: {pool_state_dict[pool_wallet_info.launcher_id]['current_difficulty']}")
print(f"Points balance: {pool_state_dict[pool_wallet_info.launcher_id]['current_points']}")
points_found_24h = [points for timestamp, points in pool_state["points_found_24h"]]
points_acknowledged_24h = [points for timestamp, points in pool_state["points_acknowledged_24h"]]
if pool_state_dict is not None:
print(f"Current difficulty: {pool_state_dict['current_difficulty']}")
print(f"Points balance: {pool_state_dict['current_points']}")
points_found_24h = [points for timestamp, points in pool_state_dict["points_found_24h"]]
points_acknowledged_24h = [points for timestamp, points in pool_state_dict["points_acknowledged_24h"]]
summed_points_found_24h = sum(points_found_24h)
summed_points_acknowledged_24h = sum(points_acknowledged_24h)
if summed_points_found_24h == 0:
@@ -159,13 +158,13 @@ async def pprint_pool_wallet_state(
success_pct = summed_points_acknowledged_24h / summed_points_found_24h
print(f"Points found (24h): {summed_points_found_24h}")
print(f"Percent Successful Points (24h): {success_pct:.2%}")
payout_instructions: str = pool_state_dict["pool_config"]["payout_instructions"]
try:
payout_address = encode_puzzle_hash(bytes32.fromhex(payout_instructions), address_prefix)
print(f"Payout instructions (pool will pay to this address): {payout_address}")
except Exception:
print(f"Payout instructions (pool will pay you with this): {payout_instructions}")
print(f"Relative lock height: {pool_wallet_info.current.relative_lock_height} blocks")
payout_instructions: str = pool_state_dict[pool_wallet_info.launcher_id]["pool_config"]["payout_instructions"]
try:
payout_address = encode_puzzle_hash(bytes32.fromhex(payout_instructions), address_prefix)
print(f"Payout instructions (pool will pay to this address): {payout_address}")
except Exception:
print(f"Payout instructions (pool will pay you with this): {payout_instructions}")
if pool_wallet_info.current.state == PoolSingletonState.LEAVING_POOL:
expected_leave_height = pool_wallet_info.singleton_block_height + pool_wallet_info.current.relative_lock_height
if pool_wallet_info.target is not None:
@@ -210,7 +209,7 @@ async def show(args: dict, wallet_client: WalletRpcClient, fingerprint: int) ->
wallet_id_passed_in,
pool_wallet_info,
address_prefix,
pool_state_dict,
pool_state_dict.get(pool_wallet_info.launcher_id),
)
else:
print(f"Wallet height: {await wallet_client.get_height_info()}")
@@ -226,7 +225,7 @@ async def show(args: dict, wallet_client: WalletRpcClient, fingerprint: int) ->
wallet_id,
pool_wallet_info,
address_prefix,
pool_state_dict,
pool_state_dict.get(pool_wallet_info.launcher_id),
)
print("")
farmer_client.close()
+146 -9
View File
@@ -455,6 +455,24 @@ def did_wallet_name_cmd(wallet_rpc_port: Optional[int], fingerprint: int, id: in
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, did_set_wallet_name))
@did_cmd.command("get_did", short_help="Get DID from 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("-i", "--id", help="Id of the wallet to use", type=int, required=True)
def did_get_did_cmd(wallet_rpc_port: Optional[int], fingerprint: int, id: int) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, get_did
extra_params = {"did_wallet_id": id}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, get_did))
@wallet_cmd.group("nft", short_help="NFT related actions")
def nft_cmd():
pass
@@ -469,11 +487,15 @@ def nft_cmd():
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:
@click.option("-di", "--did-id", help="DID Id to use", type=str)
@click.option("-n", "--name", help="Set the NFT wallet name", type=str)
def nft_wallet_create_cmd(
wallet_rpc_port: Optional[int], fingerprint: int, did_id: Optional[str], name: Optional[str]
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, create_nft_wallet
extra_params: Dict[str, Any] = {}
extra_params: Dict[str, Any] = {"did_id": did_id, "name": name}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, create_nft_wallet))
@@ -487,9 +509,17 @@ def nft_wallet_create_cmd(wallet_rpc_port: Optional[int], fingerprint: int) -> N
)
@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("-ra", "--royalty-address", help="Royalty address", type=str)
@click.option("-ta", "--target-address", help="Target address", type=str)
@click.option("--no-did-ownership", help="Disable DID ownership support", is_flag=True, default=False)
@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("-mh", "--metadata-hash", help="NFT metadata hash", type=str, default="")
@click.option("-mu", "--metadata-uris", help="Comma separated list of metadata URIs", type=str)
@click.option("-lh", "--license-hash", help="NFT license hash", type=str, default="")
@click.option("-lu", "--license-uris", help="Comma separated list of license URIs", type=str)
@click.option("-st", "--series-total", help="NFT series total number", type=int, default=1, show_default=True)
@click.option("-sn", "--series-number", help="NFT seriese number", type=int, default=1, show_default=True)
@click.option(
"-m",
"--fee",
@@ -499,24 +529,60 @@ def nft_wallet_create_cmd(wallet_rpc_port: Optional[int], fingerprint: int) -> N
show_default=True,
callback=validate_fee,
)
@click.option(
"-rp",
"--royalty-percentage-fraction",
help="NFT royalty percentage fraction in basis points. Example: 175 would represent 1.75%",
type=int,
default=0,
show_default=True,
)
def nft_mint_cmd(
wallet_rpc_port: Optional[int],
fingerprint: int,
id: int,
artist_address: str,
royalty_address: Optional[str],
target_address: Optional[str],
no_did_ownership: bool,
hash: str,
uris: str,
metadata_hash: Optional[str],
metadata_uris: Optional[str],
license_hash: Optional[str],
license_uris: Optional[str],
series_total: Optional[int],
series_number: Optional[int],
fee: str,
royalty_percentage_fraction: int,
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, mint_nft
if metadata_uris is None:
metadata_uris_list = []
else:
metadata_uris_list = [mu.strip() for mu in metadata_uris.split(",")]
if license_uris is None:
license_uris_list = []
else:
license_uris_list = [lu.strip() for lu in license_uris.split(",")]
extra_params = {
"wallet_id": id,
"artist_address": artist_address,
"royalty_address": royalty_address,
"target_address": target_address,
"no_did_ownership": no_did_ownership,
"hash": hash,
"uris": [u.strip() for u in uris.split(",")],
"metadata_hash": metadata_hash,
"metadata_uris": metadata_uris_list,
"license_hash": license_hash,
"license_uris": license_uris_list,
"series_total": series_total,
"series_number": series_number,
"fee": fee,
"royalty_percentage": royalty_percentage_fraction,
}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, mint_nft))
@@ -532,7 +598,9 @@ def nft_mint_cmd(
@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("-u", "--uri", help="URI to add to the NFT", type=str)
@click.option("-mu", "--metadata-uri", help="Metadata URI to add to the NFT", type=str)
@click.option("-lu", "--license-uri", help="License URI to add to the NFT", type=str)
@click.option(
"-m",
"--fee",
@@ -548,6 +616,8 @@ def nft_add_uri_cmd(
id: int,
nft_coin_id: str,
uri: str,
metadata_uri: str,
license_uri: str,
fee: str,
) -> None:
import asyncio
@@ -557,6 +627,8 @@ def nft_add_uri_cmd(
"wallet_id": id,
"nft_coin_id": nft_coin_id,
"uri": uri,
"metadata_uri": metadata_uri,
"license_uri": license_uri,
"fee": fee,
}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, add_uri_to_nft))
@@ -573,7 +645,7 @@ def nft_add_uri_cmd(
@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("-ta", "--target-address", help="Target recipient wallet address", type=str, required=True)
@click.option(
"-m",
"--fee",
@@ -588,7 +660,7 @@ def nft_transfer_cmd(
fingerprint: int,
id: int,
nft_coin_id: str,
artist_address: str,
target_address: str,
fee: str,
) -> None:
import asyncio
@@ -597,7 +669,7 @@ def nft_transfer_cmd(
extra_params = {
"wallet_id": id,
"nft_coin_id": nft_coin_id,
"artist_address": artist_address,
"target_address": target_address,
"fee": fee,
}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, transfer_nft))
@@ -619,3 +691,68 @@ def nft_list_cmd(wallet_rpc_port: Optional[int], fingerprint: int, id: int) -> N
extra_params = {"wallet_id": id}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, list_nfts))
@nft_cmd.command("set_did", short_help="Set a DID on 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("-di", "--did-id", help="DID Id to set on the NFT", type=str, required=True)
@click.option("-ni", "--nft-coin-id", help="Id of the NFT coin to set the DID on", 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_set_did_cmd(
wallet_rpc_port: Optional[int],
fingerprint: int,
id: int,
did_id: str,
nft_coin_id: str,
fee: str,
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, set_nft_did
extra_params = {
"wallet_id": id,
"did_id": did_id,
"nft_coin_id": nft_coin_id,
"fee": fee,
}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, set_nft_did))
@nft_cmd.command("get_info", short_help="Get NFT information")
@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("-ni", "--nft-coin-id", help="Id of the NFT coin to get information on", type=str, required=True)
def nft_get_info_cmd(
wallet_rpc_port: Optional[int],
fingerprint: int,
nft_coin_id: str,
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, get_nft_info
extra_params = {
"nft_coin_id": nft_coin_id,
}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, get_nft_info))
+218 -39
View File
@@ -14,11 +14,13 @@ from chia.rpc.wallet_rpc_client import WalletRpcClient
from chia.server.outbound_message import NodeType
from chia.server.start_wallet import SERVICE_NAME
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.bech32m import encode_puzzle_hash
from chia.util.bech32m import bech32_decode, decode_puzzle_hash, encode_puzzle_hash
from chia.util.config import load_config
from chia.util.default_root import DEFAULT_ROOT_PATH
from chia.util.ints import uint16, uint32, uint64
from chia.cmds.cmds_util import transaction_submitted_msg, transaction_status_msg
from chia.wallet.did_wallet.did_info import DID_HRP
from chia.wallet.nft_wallet.nft_info import NFT_HRP, NFTInfo
from chia.wallet.trade_record import TradeRecord
from chia.wallet.trading.offer import Offer
from chia.wallet.trading.trade_status import TradeStatus
@@ -282,22 +284,65 @@ async def make_offer(args: dict, wallet_client: WalletRpcClient, fingerprint: in
print("Not creating offer: Must be offering and requesting at least one asset")
else:
offer_dict: Dict[Union[uint32, str], int] = {}
driver_dict: Dict[str, Any] = {}
printable_dict: Dict[str, Tuple[str, int, int]] = {} # Dict[asset_name, Tuple[amount, unit, multiplier]]
nft_warning: bool = False
for item in [*offers, *requests]:
wallet_id, amount = tuple(item.split(":")[0:2])
if int(wallet_id) == 1:
name: str = "XCH"
unit: int = units["chia"]
else:
name = await wallet_client.get_cat_name(wallet_id)
unit = units["cat"]
name, amount = tuple(item.split(":")[0:2])
try:
id: Union[uint32, str] = bytes32.from_hexstr(name).hex()
unit = 1
except ValueError:
try:
hrp, _ = bech32_decode(name)
if hrp == "nft":
coin_id = decode_puzzle_hash(name)
unit = 1
info = NFTInfo.from_json_dict((await wallet_client.get_nft_info(coin_id.hex()))["nft_info"])
nft_warning = True
id = info.launcher_id.hex()
assert isinstance(id, str)
if item in requests:
driver_dict[id] = {
"type": "singleton",
"launcher_id": "0x" + id,
"launcher_ph": "0x" + info.launcher_puzhash.hex(),
"also": {
"type": "metadata",
"metadata": info.chain_info,
"updater_hash": "0x" + info.updater_puzhash.hex(),
},
}
if info.supports_did:
driver_dict[id]["also"]["also"] = {
"type": "ownership",
"owner": "()",
"transfer_program": {
"type": "royalty transfer program",
"launcher_id": "0x" + info.launcher_id.hex(),
"royalty_address": "0x" + info.royalty_puzzle_hash.hex(),
"royalty_percentage": str(info.royalty_percentage),
},
}
else:
id = decode_puzzle_hash(name).hex()
assert hrp is not None
unit = units[hrp]
except ValueError:
id = uint32(int(name))
if id == 1:
name = "XCH"
unit = units["chia"]
else:
name = await wallet_client.get_cat_name(str(id))
unit = units["cat"]
multiplier: int = -1 if item in offers else 1
printable_dict[name] = (amount, unit, multiplier)
if uint32(int(wallet_id)) in offer_dict:
if id in offer_dict:
print("Not creating offer: Cannot offer and request the same asset in a trade")
break
else:
offer_dict[uint32(int(wallet_id))] = int(Decimal(amount) * unit) * multiplier
offer_dict[id] = int(Decimal(amount) * unit) * multiplier
else:
print("Creating Offer")
print("--------------")
@@ -313,11 +358,22 @@ async def make_offer(args: dict, wallet_client: WalletRpcClient, fingerprint: in
if multiplier > 0:
print(f" - {amount} {name} ({int(Decimal(amount) * unit)} mojos)")
if nft_warning:
nft_confirmation = input(
"Offers for NFTs will have royalties automatically added. "
+ "Are you sure you would like to continue? (y/n): "
)
if nft_confirmation not in ["y", "yes"]:
print("Not creating offer...")
return
confirmation = input("Confirm (y/n): ")
if confirmation not in ["y", "yes"]:
print("Not creating offer...")
else:
offer, trade_record = await wallet_client.create_offer_for_ids(offer_dict, fee=fee)
offer, trade_record = await wallet_client.create_offer_for_ids(
offer_dict, driver_dict=driver_dict, fee=fee
)
if offer is not None:
with open(pathlib.Path(filepath), "w") as file:
file.write(offer.to_bech32())
@@ -539,13 +595,23 @@ async def print_balances(args: dict, wallet_client: WalletRpcClient, fingerprint
balances["unconfirmed_wallet_balance"], scale, address_prefix
)
spendable_balance: str = print_balance(balances["spendable_balance"], scale, address_prefix)
my_did: Optional[str] = None
print()
print(f"{summary['name']}:")
print(f"{indent}{'-Total Balance:'.ljust(23)} {total_balance}")
print(f"{indent}{'-Pending Total Balance:'.ljust(23)} " f"{unconfirmed_wallet_balance}")
print(f"{indent}{'-Spendable:'.ljust(23)} {spendable_balance}")
print(f"{indent}{'-Type:'.ljust(23)} {typ.name}")
if len(asset_id) > 0:
if typ == WalletType.DECENTRALIZED_ID:
get_did_response = await wallet_client.get_did_id(wallet_id)
my_did = get_did_response["my_did"]
print(f"{indent}{'-DID ID:'.ljust(23)} {my_did}")
elif typ == WalletType.NFT:
get_did_response = await wallet_client.get_nft_wallet_did(wallet_id)
my_did = get_did_response["did_id"]
if my_did is not None and len(my_did) > 0:
print(f"{indent}{'-DID ID:'.ljust(23)} {my_did}")
elif len(asset_id) > 0:
print(f"{indent}{'-Asset ID:'.ljust(23)} {asset_id}")
print(f"{indent}{'-Wallet ID:'.ljust(23)} {wallet_id}")
@@ -647,7 +713,7 @@ async def execute_with_wallet(
async def create_did_wallet(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
amount = args["amount"]
fee = args["fee"]
fee: int = int(Decimal(args["fee"]) * units["chia"])
name = args["name"]
try:
response = await wallet_client.create_new_did_wallet(amount, fee, name)
@@ -669,9 +735,23 @@ async def did_set_wallet_name(args: Dict, wallet_client: WalletRpcClient, finger
print(f"Failed to set DID wallet name: {e}")
async def create_nft_wallet(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
async def get_did(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
did_wallet_id: int = args["did_wallet_id"]
try:
response = await wallet_client.create_new_nft_wallet(None)
response = await wallet_client.get_did_id(did_wallet_id)
my_did = response["my_did"]
coin_id = response["coin_id"]
print(f"{'DID:'.ljust(23)} {my_did}")
print(f"{'Coin ID:'.ljust(23)} {coin_id}")
except Exception as e:
print(f"Failed to get DID: {e}")
async def create_nft_wallet(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
did_id = args["did_id"]
name = args["name"]
try:
response = await wallet_client.create_new_nft_wallet(did_id, name)
wallet_id = response["wallet_id"]
print(f"Successfully created an NFT wallet with id {wallet_id} on key {fingerprint}")
except Exception as e:
@@ -679,13 +759,51 @@ async def create_nft_wallet(args: Dict, wallet_client: WalletRpcClient, fingerpr
async def mint_nft(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
wallet_id = args["wallet_id"]
royalty_address = args["royalty_address"]
target_address = args["target_address"]
no_did_ownership = args["no_did_ownership"]
hash = args["hash"]
uris = args["uris"]
metadata_hash = args["metadata_hash"]
metadata_uris = args["metadata_uris"]
license_hash = args["license_hash"]
license_uris = args["license_uris"]
series_total = args["series_total"]
series_number = args["series_number"]
fee: int = int(Decimal(args["fee"]) * units["chia"])
royalty_percentage = args["royalty_percentage"]
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)
response = await wallet_client.get_nft_wallet_did(wallet_id)
wallet_did = response["did_id"]
wallet_has_did = wallet_did is not None
did_id: Optional[str] = wallet_did
# Handle the case when the user wants to disable DID ownership
if no_did_ownership:
if wallet_has_did:
raise ValueError("Disabling DID ownership is not supported for this NFT wallet, it does have a DID")
else:
did_id = None
else:
if not wallet_has_did:
did_id = ""
response = await wallet_client.mint_nft(
wallet_id,
royalty_address,
target_address,
hash,
uris,
metadata_hash,
metadata_uris,
license_hash,
license_uris,
series_total,
series_number,
fee,
royalty_percentage,
did_id,
)
spend_bundle = response["spend_bundle"]
print(f"NFT minted Successfully with spend bundle: {spend_bundle}")
except Exception as e:
@@ -697,8 +815,23 @@ async def add_uri_to_nft(args: Dict, wallet_client: WalletRpcClient, fingerprint
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)
metadata_uri = args["metadata_uri"]
license_uri = args["license_uri"]
if len([x for x in (uri, metadata_uri, license_uri) if x is not None]) > 1:
raise ValueError("You must provide only one of the URI flags")
if uri is not None and len(uri) > 0:
key = "u"
uri_value = uri
elif metadata_uri is not None and len(metadata_uri) > 0:
key = "mu"
uri_value = metadata_uri
elif license_uri is not None and len(license_uri) > 0:
key = "lu"
uri_value = license_uri
else:
raise ValueError("You must provide at least one of the URI flags")
fee: int = int(Decimal(args["fee"]) * units["chia"])
response = await wallet_client.add_uri_to_nft(wallet_id, nft_coin_id, key, uri_value, fee)
spend_bundle = response["spend_bundle"]
print(f"URI added successfully with spend bundle: {spend_bundle}")
except Exception as e:
@@ -709,37 +842,83 @@ async def transfer_nft(args: Dict, wallet_client: WalletRpcClient, fingerprint:
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)
target_address = args["target_address"]
fee: int = int(Decimal(args["fee"]) * units["chia"])
response = await wallet_client.transfer_nft(wallet_id, nft_coin_id, target_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}")
def print_nft_info(nft: NFTInfo) -> None:
indent: str = " "
owner_did = None if nft.owner_did is None else encode_puzzle_hash(nft.owner_did, DID_HRP)
print()
print(f"{'NFT identifier:'.ljust(26)} {encode_puzzle_hash(nft.launcher_id, NFT_HRP)}")
print(f"{'Launcher coin ID:'.ljust(26)} {nft.launcher_id}")
print(f"{'Launcher puzhash:'.ljust(26)} {nft.launcher_puzhash}")
print(f"{'Current NFT coin ID:'.ljust(26)} {nft.nft_coin_id}")
print(f"{'On-chain data/info:'.ljust(26)} {nft.chain_info}")
print(f"{'Owner DID:'.ljust(26)} {owner_did}")
print(f"{'Royalty percentage:'.ljust(26)} {nft.royalty_percentage}")
print(f"{'Royalty puzhash:'.ljust(26)} {nft.royalty_puzzle_hash}")
print(f"{'NFT content hash:'.ljust(26)} {nft.data_hash.hex()}")
print(f"{'Metadata hash:'.ljust(26)} {nft.metadata_hash.hex()}")
print(f"{'License hash:'.ljust(26)} {nft.license_hash.hex()}")
print(f"{'NFT series total:'.ljust(26)} {nft.series_total}")
print(f"{'Current NFT number in the series:'.ljust(26)} {nft.series_number}")
print(f"{'Metadata updater puzhash:'.ljust(26)} {nft.updater_puzhash}")
print(f"{'NFT minting block height:'.ljust(26)} {nft.mint_height}")
print(f"{'Inner puzzle supports DID:'.ljust(26)} {nft.supports_did}")
print(f"{'NFT is pending for a transaction:'.ljust(26)} {nft.pending_transaction}")
print()
print("URIs:")
for uri in nft.data_uris:
print(f"{indent}{uri}")
print()
print("Metadata URIs:")
for metadata_uri in nft.metadata_uris:
print(f"{indent}{metadata_uri}")
print()
print("License URIs:")
for license_uri in nft.license_uris:
print(f"{indent}{license_uri}")
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}")
print_nft_info(nft)
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}")
async def set_nft_did(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
wallet_id = args["wallet_id"]
did_id = args["did_id"]
nft_coin_id = args["nft_coin_id"]
fee: int = int(Decimal(args["fee"]) * units["chia"])
try:
response = await wallet_client.set_nft_did(wallet_id, did_id, nft_coin_id, fee)
spend_bundle = response["spend_bundle"]
print(f"Transaction to set DID on NFT has been initiated with: {spend_bundle}")
except Exception as e:
print(f"Failed to set DID on NFT: {e}")
async def get_nft_info(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
nft_coin_id = args["nft_coin_id"]
try:
response = await wallet_client.get_nft_info(nft_coin_id)
nft_info = NFTInfo.from_json_dict(response["nft_info"])
print_nft_info(nft_info)
except Exception as e:
print(f"Failed to get NFT info: {e}")
+7 -1
View File
@@ -15,6 +15,7 @@ from chia.types.peer_info import PeerInfo
from chia.util.byte_types import hexstr_to_bytes
from chia.util.ints import uint16
from chia.util.json_util import dict_to_json_str
from chia.util.network import select_port
from chia.util.ws_message import create_payload, create_payload_dict, format_response, pong
log = logging.getLogger(__name__)
@@ -336,7 +337,12 @@ async def start_rpc_server(
site = web.TCPSite(runner, self_hostname, int(rpc_port), ssl_context=rpc_server.ssl_context)
await site.start()
rpc_port = runner.addresses[0][1]
#
# On a dual-stack system, we want to get the (first) IPv4 port unless
# prefer_ipv6 is set in which case we use the IPv6 port
#
if rpc_port == 0:
rpc_port = select_port(root_path, runner.addresses)
async def cleanup():
await rpc_server.stop()
+337 -73
View File
@@ -12,17 +12,19 @@ from chia.data_layer.data_layer_wallet import DataLayerWallet
from chia.pools.pool_wallet import PoolWallet
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.protocols.wallet_protocol import CoinState
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.coin_spend import CoinSpend
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.config import load_config
from chia.util.ints import uint8, uint32, uint64
from chia.util.ints import uint8, uint32, uint64, uint16
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
@@ -35,9 +37,13 @@ from chia.wallet.derive_keys import (
master_sk_to_singleton_owner_sk,
match_address_to_sk,
)
from chia.wallet.did_wallet.did_info import DID_HRP
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.nft_wallet import nft_puzzles
from chia.wallet.nft_wallet.nft_info import NFT_HRP, NFTInfo
from chia.wallet.nft_wallet.nft_puzzles import get_metadata_and_phs
from chia.wallet.nft_wallet.nft_wallet import NFTWallet, NFTCoinInfo
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.rl_wallet.rl_wallet import RLWallet
@@ -132,6 +138,12 @@ class WalletRpcApi:
# NFT Wallet
"/nft_mint_nft": self.nft_mint_nft,
"/nft_get_nfts": self.nft_get_nfts,
"/nft_get_by_did": self.nft_get_by_did,
"/nft_set_nft_did": self.nft_set_nft_did,
"/nft_set_nft_status": self.nft_set_nft_status,
"/nft_get_wallet_did": self.nft_get_wallet_did,
"/nft_get_wallets_with_dids": self.nft_get_wallets_with_dids,
"/nft_get_info": self.nft_get_info,
"/nft_transfer_nft": self.nft_transfer_nft,
"/nft_add_uri": self.nft_add_uri,
# RL wallet
@@ -438,13 +450,17 @@ class WalletRpcApi:
async def get_wallets(self, request: Dict):
assert self.service.wallet_state_manager is not None
include_data: bool = request.get("include_data", True)
wallet_type: Optional[WalletType] = None
if "type" in request:
wallet_type = WalletType(request["type"])
wallets: List[WalletInfo] = await self.service.wallet_state_manager.get_all_wallet_info_entries(wallet_type)
if not include_data:
result: List[WalletInfo] = []
for wallet in wallets:
result.append(WalletInfo(wallet.id, wallet.name, wallet.type, ""))
wallets = result
return {"wallets": wallets}
async def create_new_wallet(self, request: Dict):
@@ -523,7 +539,7 @@ class WalletRpcApi:
backup_dids = []
num_needed = 0
for d in request["backup_dids"]:
backup_dids.append(hexstr_to_bytes(d))
backup_dids.append(decode_puzzle_hash(d))
if len(backup_dids) > 0:
num_needed = uint64(request["num_of_backup_ids_needed"])
metadata: Dict[str, str] = {}
@@ -543,7 +559,7 @@ class WalletRpcApi:
uint64(request.get("fee", 0)),
)
my_did = did_wallet.get_my_DID()
my_did = encode_puzzle_hash(bytes32.fromhex(did_wallet.get_my_DID()), DID_HRP)
return {
"success": True,
"type": did_wallet.type(),
@@ -580,18 +596,20 @@ class WalletRpcApi:
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
did_id: Optional[bytes32] = None
if "did_id" in request and request["did_id"] is not None:
did_id = decode_puzzle_hash(request["did_id"])
if wallet.type() == WalletType.NFT and wallet.get_did() == did_id:
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,
wallet_state_manager, main_wallet, did_id, request.get("name", None)
)
return {
"success": True,
@@ -979,13 +997,14 @@ class WalletRpcApi:
# This driver_dict construction is to maintain backward compatibility where everything is assumed to be a CAT
driver_dict: Dict[bytes32, PuzzleInfo] = {}
if driver_dict_str is None:
for key in offer:
try:
driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(
{"type": AssetType.CAT.value, "tail": "0x" + key}
)
except ValueError:
pass
for key, amount in offer.items():
if amount > 0:
try:
driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(
{"type": AssetType.CAT.value, "tail": "0x" + key}
)
except ValueError:
pass
else:
for key, value in driver_dict_str.items():
driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(value)
@@ -1121,9 +1140,9 @@ class WalletRpcApi:
async def did_set_wallet_name(self, request):
assert self.service.wallet_state_manager is not None
wallet_id = int(request["wallet_id"])
wallet_id = uint32(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
if wallet.type() == WalletType.DISTRIBUTED_ID:
if wallet.type() == WalletType.DECENTRALIZED_ID:
await wallet.set_name(str(request["name"]))
return {"success": True, "wallet_id": wallet_id}
else:
@@ -1131,18 +1150,18 @@ class WalletRpcApi:
async def did_get_wallet_name(self, request):
assert self.service.wallet_state_manager is not None
wallet_id = int(request["wallet_id"])
wallet_id = uint32(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_id = uint32(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(_))
recovery_list.append(decode_puzzle_hash(_))
if "num_verifications_required" in request:
new_amount_verifications_required = uint64(request["num_verifications_required"])
else:
@@ -1157,26 +1176,29 @@ class WalletRpcApi:
return {"success": success}
async def did_update_metadata(self, request):
wallet_id = int(request["wallet_id"])
wallet_id = uint32(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
if wallet.type() != WalletType.DECENTRALIZED_ID.value:
return {"success": False, "error": f"Wallet with id {wallet_id} is not a DID one"}
metadata: Dict[str, str] = {}
success: bool = False
if "metadata" in request:
if type(request["metadata"]) is dict:
metadata = request["metadata"]
if "metadata" in request and 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()
spend_bundle = await wallet.create_update_spend(uint64(request.get("fee", 0)))
if spend_bundle is not None:
success = True
return {"success": success}
return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle}
else:
return {"success": False, "error": "Couldn't create an update spend bundle."}
else:
return {"success": False, "error": f"Couldn't update metadata with input: {metadata}"}
async def did_get_did(self, request):
wallet_id = int(request["wallet_id"])
wallet_id = uint32(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
my_did: str = wallet.get_my_DID()
my_did: str = encode_puzzle_hash(bytes32.fromhex(wallet.get_my_DID()), DID_HRP)
async with self.service.wallet_state_manager.lock:
coins = await wallet.select_coins(1)
if coins is None or coins == set():
@@ -1186,21 +1208,21 @@ class WalletRpcApi:
return {"success": True, "wallet_id": wallet_id, "my_did": my_did, "coin_id": coin.name()}
async def did_get_recovery_list(self, request):
wallet_id = int(request["wallet_id"])
wallet_id = uint32(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
recovery_list = wallet.did_info.backup_ids
recover_hex_list = []
for _ in recovery_list:
recover_hex_list.append(_.hex())
recovery_dids = []
for backup_id in recovery_list:
recovery_dids.append(encode_puzzle_hash(backup_id, DID_HRP))
return {
"success": True,
"wallet_id": wallet_id,
"recovery_list": recover_hex_list,
"recovery_list": recovery_dids,
"num_required": wallet.did_info.num_of_backup_ids_needed,
}
async def did_get_metadata(self, request):
wallet_id = int(request["wallet_id"])
wallet_id = uint32(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
metadata = json.loads(wallet.did_info.metadata)
return {
@@ -1210,11 +1232,11 @@ class WalletRpcApi:
}
async def did_recovery_spend(self, request):
wallet_id = int(request["wallet_id"])
wallet_id = uint32(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
if len(request["attest_data"]) < wallet.did_info.num_of_backup_ids_needed:
return {"success": False, "reason": "insufficient messages"}
spend_bundle = None
async with self.service.wallet_state_manager.lock:
(
info_list,
@@ -1233,31 +1255,34 @@ class WalletRpcApi:
assert wallet.did_info.temp_puzhash is not None
puzhash = wallet.did_info.temp_puzhash
success = await wallet.recovery_spend(
spend_bundle = await wallet.recovery_spend(
wallet.did_info.temp_coin,
puzhash,
info_list,
pubkey,
message_spend_bundle,
)
return {"success": success}
if spend_bundle:
return {"success": True, "spend_bundle": spend_bundle}
else:
return {"success": False}
async def did_get_pubkey(self, request):
wallet_id = int(request["wallet_id"])
wallet_id = uint32(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
pubkey = bytes((await wallet.wallet_state_manager.get_unused_derivation_record(wallet_id)).pubkey).hex()
return {"success": True, "pubkey": pubkey}
async def did_create_attest(self, request):
wallet_id = int(request["wallet_id"])
wallet_id = uint32(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
async with self.service.wallet_state_manager.lock:
info = await wallet.get_info_for_recovery()
coin = hexstr_to_bytes(request["coin_name"])
coin = bytes32.from_hexstr(request["coin_name"])
pubkey = G1Element.from_bytes(hexstr_to_bytes(request["pubkey"]))
spend_bundle, attest_data = await wallet.create_attestment(
coin,
hexstr_to_bytes(request["puzhash"]),
bytes32.from_hexstr(request["puzhash"]),
pubkey,
)
if info is not None and spend_bundle is not None:
@@ -1271,9 +1296,9 @@ class WalletRpcApi:
return {"success": False}
async def did_get_information_needed_for_recovery(self, request):
wallet_id = int(request["wallet_id"])
wallet_id = uint32(request["wallet_id"])
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
my_did = did_wallet.get_my_DID()
my_did = encode_puzzle_hash(bytes32.from_hexstr(did_wallet.get_my_DID()), DID_HRP)
coin_name = did_wallet.did_info.temp_coin.name().hex()
return {
"success": True,
@@ -1286,9 +1311,9 @@ class WalletRpcApi:
}
async def did_get_current_coin_info(self, request):
wallet_id = int(request["wallet_id"])
wallet_id = uint32(request["wallet_id"])
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
my_did = did_wallet.get_my_DID()
my_did = encode_puzzle_hash(bytes32.from_hexstr(did_wallet.get_my_DID()), DID_HRP)
did_coin_threeple = await did_wallet.get_info_for_recovery()
assert my_did is not None
assert did_coin_threeple is not None
@@ -1302,7 +1327,7 @@ class WalletRpcApi:
}
async def did_create_backup_file(self, request):
wallet_id = int(request["wallet_id"])
wallet_id = uint32(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()}
@@ -1310,7 +1335,7 @@ class WalletRpcApi:
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"])
wallet_id = uint32(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:
@@ -1333,22 +1358,58 @@ class WalletRpcApi:
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()
if nft_wallet.type() != WalletType.NFT.value:
return {"success": False, "error": f"Wallet with id {wallet_id} is not an NFT one"}
royalty_address = request.get("royalty_address")
if isinstance(royalty_address, str):
royalty_puzhash = decode_puzzle_hash(royalty_address)
elif royalty_address is None:
royalty_puzhash = await nft_wallet.standard_wallet.get_new_puzzlehash()
else:
puzzle_hash = address
metadata = Program.to(
[
("u", request["uris"]),
("h", hexstr_to_bytes(request["hash"])),
]
)
royalty_puzhash = royalty_address
target_address = request.get("target_address")
if isinstance(target_address, str):
target_puzhash = decode_puzzle_hash(target_address)
elif target_address is None:
target_puzhash = await nft_wallet.standard_wallet.get_new_puzzlehash()
else:
target_puzhash = target_address
if "uris" not in request:
return {"success": False, "error": "Data URIs is required"}
if not isinstance(request["uris"], list):
return {"success": False, "error": "Data URIs must be a list"}
if not isinstance(request.get("meta_uris", []), list):
return {"success": False, "error": "Metadata URIs must be a list"}
if not isinstance(request.get("license_uris", []), list):
return {"success": False, "error": "License URIs must be a list"}
metadata_list = [
("u", request["uris"]),
("h", hexstr_to_bytes(request["hash"])),
("mu", request.get("meta_uris", [])),
("lu", request.get("license_uris", [])),
("sn", uint64(request.get("series_number", 1))),
("st", uint64(request.get("series_total", 1))),
]
if "meta_hash" in request and len(request["meta_hash"]) > 0:
metadata_list.append(("mh", hexstr_to_bytes(request["meta_hash"])))
if "license_hash" in request and len(request["license_hash"]) > 0:
metadata_list.append(("lh", hexstr_to_bytes(request["license_hash"])))
metadata = Program.to(metadata_list)
fee = uint64(request.get("fee", 0))
spend_bundle = await nft_wallet.generate_new_nft(metadata, puzzle_hash, fee=fee)
did_id = request.get("did_id", None)
if did_id is not None:
if did_id == "":
did_id = bytes()
else:
did_id = decode_puzzle_hash(did_id)
spend_bundle = await nft_wallet.generate_new_nft(
metadata,
target_puzhash,
royalty_puzhash,
uint16(request.get("royalty_percentage", 0)),
did_id,
fee,
)
return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle}
async def nft_get_nfts(self, request) -> Dict:
@@ -1358,9 +1419,93 @@ class WalletRpcApi:
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))
nft_info_list.append(nft_puzzles.get_nft_info_from_puzzle(nft))
return {"wallet_id": wallet_id, "success": True, "nft_list": nft_info_list}
async def nft_set_nft_did(self, request):
try:
assert self.service.wallet_state_manager is not None
wallet_id = uint32(request["wallet_id"])
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
did_id = request.get("did_id", "")
if did_id == "":
did_id = b""
else:
did_id = decode_puzzle_hash(did_id)
nft_coin_info = nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(request["nft_coin_id"]))
if not nft_puzzles.get_nft_info_from_puzzle(nft_coin_info).supports_did:
return {"success": False, "error": "The NFT doesn't support setting a DID."}
fee = uint64(request.get("fee", 0))
spend_bundle = await nft_wallet.set_nft_did(nft_coin_info, did_id, fee=fee)
return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle}
except Exception as e:
log.exception(f"Failed to set DID on NFT: {e}")
return {"success": False, "error": f"Failed to set DID on NFT: {e}"}
async def nft_get_by_did(self, request) -> Dict:
did_id: Optional[bytes32] = None
if "did_id" in request:
did_id = decode_puzzle_hash(request["did_id"])
assert self.service.wallet_state_manager is not None
for wallet in self.service.wallet_state_manager.wallets.values():
if isinstance(wallet, NFTWallet) and wallet.get_did() == did_id:
return {"wallet_id": wallet.wallet_id, "success": True}
return {"error": f"Cannot find a NFT wallet DID = {did_id}", "success": False}
async def nft_get_wallet_did(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]
if nft_wallet is not None:
if nft_wallet.type() != WalletType.NFT.value:
return {"success": False, "error": f"Wallet {wallet_id} is not an NFT wallet"}
did_bytes: Optional[bytes32] = nft_wallet.get_did()
did_id = ""
if did_bytes is not None:
did_id = encode_puzzle_hash(did_bytes, DID_HRP)
return {"success": True, "did_id": None if len(did_id) == 0 else did_id}
return {"success": False, "error": f"Wallet {wallet_id} not found"}
async def nft_get_wallets_with_dids(self, request) -> Dict:
assert self.service.wallet_state_manager is not None
all_wallets = self.service.wallet_state_manager.wallets.values()
did_wallets_by_did_id: Dict[bytes32, uint32] = {
wallet.did_info.origin_coin.name(): wallet.id()
for wallet in all_wallets
if wallet.type() == uint8(WalletType.DECENTRALIZED_ID) and wallet.did_info.origin_coin is not None
}
did_nft_wallets: List[Dict] = []
for wallet in all_wallets:
if isinstance(wallet, NFTWallet):
nft_wallet_did: Optional[bytes32] = wallet.get_did()
if nft_wallet_did is not None:
did_wallet_id: uint32 = did_wallets_by_did_id.get(nft_wallet_did, uint32(0))
if did_wallet_id == 0:
log.warning(f"NFT wallet {wallet.id()} has DID {nft_wallet_did.hex()} but no DID wallet")
else:
did_nft_wallets.append(
{
"wallet_id": wallet.id(),
"did_id": encode_puzzle_hash(nft_wallet_did, DID_HRP),
"did_wallet_id": did_wallet_id,
}
)
return {"success": True, "nft_wallets": did_nft_wallets}
async def nft_set_nft_status(self, request) -> Dict:
try:
wallet_id: uint32 = uint32(request["wallet_id"])
coin_id: bytes32 = bytes32.from_hexstr(request["coin_id"])
status: bool = request["in_transaction"]
assert self.service.wallet_state_manager is not None
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
if nft_wallet is not None:
await nft_wallet.update_coin_status(coin_id, status)
return {"success": True}
return {"success": False, "error": "NFT wallet doesn't exist."}
except Exception as e:
return {"success": False, "error": f"Cannot change the status of the NFT.{e}"}
async def nft_transfer_nft(self, request):
assert self.service.wallet_state_manager is not None
wallet_id = uint32(request["wallet_id"])
@@ -1371,23 +1516,142 @@ class WalletRpcApi:
return dict(success=False, error="target_address parameter missing")
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"]))
nft_coin_id = request["nft_coin_id"]
if nft_coin_id.startswith(NFT_HRP):
nft_coin_id = decode_puzzle_hash(nft_coin_id)
else:
nft_coin_id = bytes32.from_hexstr(nft_coin_id)
nft_coin_info = nft_wallet.get_nft_coin_by_id(nft_coin_id)
fee = uint64(request.get("fee", 0))
spend_bundle = await nft_wallet.transfer_nft(nft_coin_info, puzzle_hash, fee=fee)
txs = await nft_wallet.generate_signed_transaction(
[nft_coin_info.coin.amount],
[puzzle_hash],
coins={nft_coin_info.coin},
fee=fee,
new_owner=b"",
new_did_inner_hash=b"",
)
spend_bundle: Optional[SpendBundle] = None
for tx in txs:
if tx.spend_bundle is not None:
spend_bundle = tx.spend_bundle
await self.service.wallet_state_manager.add_pending_transaction(tx)
await nft_wallet.update_coin_status(nft_coin_info.coin.name(), True)
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_get_info(self, request: Dict):
assert self.service.wallet_state_manager is not None
if "coin_id" not in request:
return {"success": False, "error": "Coin ID is required."}
coin_id = request["coin_id"]
if coin_id.startswith(NFT_HRP):
coin_id = decode_puzzle_hash(coin_id)
else:
coin_id = bytes32.from_hexstr(coin_id)
peer = self.service.wallet_state_manager.wallet_node.get_full_node_peer()
if peer is None:
return {"success": False, "error": "Cannot find a full node peer."}
# Get coin state
coin_state_list: List[CoinState] = await self.service.wallet_state_manager.wallet_node.get_coin_state(
[coin_id], peer=peer
)
if coin_state_list is None or len(coin_state_list) < 1:
return {"success": False, "error": f"Coin record 0x{coin_id.hex()} not found"}
coin_state: CoinState = coin_state_list[0]
if request.get("latest", True):
# Find the unspent coin
while coin_state.spent_height is not None:
coin_state_list = await self.service.wallet_state_manager.wallet_node.fetch_children(
peer, coin_state.coin.name()
)
odd_coin = 0
for coin in coin_state_list:
if coin.coin.amount % 2 == 1:
odd_coin += 1
if odd_coin > 1:
return {"success": False, "error": "This is not a singleton, multiple children coins found."}
if odd_coin == 0:
return {"success": False, "error": "Cannot find child coin, please wait then retry."}
coin_state = coin_state_list[0]
# Get parent coin
parent_coin_state_list: List[CoinState] = await self.service.wallet_state_manager.wallet_node.get_coin_state(
[coin_state.coin.parent_coin_info], peer=peer
)
if parent_coin_state_list is None or len(parent_coin_state_list) < 1:
return {
"success": False,
"error": f"Parent coin record 0x{coin_state.coin.parent_coin_info.hex()} not found",
}
parent_coin_state: CoinState = parent_coin_state_list[0]
coin_spend: CoinSpend = await self.service.wallet_state_manager.wallet_node.fetch_puzzle_solution(
peer, parent_coin_state.spent_height, parent_coin_state.coin
)
# convert to NFTInfo
try:
# Check if the metadata is updated
full_puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal))
uncurried_nft: UncurriedNFT = UncurriedNFT.uncurry(full_puzzle)
metadata, p2_puzzle_hash = get_metadata_and_phs(uncurried_nft, coin_spend.solution)
# Note: This is not the actual unspent NFT full puzzle.
# There is no way to rebuild the full puzzle in a different wallet.
# But it shouldn't have impact on generating the NFTInfo, since inner_puzzle is not used there.
if uncurried_nft.supports_did:
inner_puzzle = nft_puzzles.recurry_nft_puzzle(
uncurried_nft, coin_spend.solution.to_program(), uncurried_nft.p2_puzzle
)
else:
inner_puzzle = uncurried_nft.p2_puzzle
full_puzzle = nft_puzzles.create_full_puzzle(
uncurried_nft.singleton_launcher_id,
metadata,
uncurried_nft.metadata_updater_hash,
inner_puzzle,
)
# Get launcher coin
launcher_coin: List[CoinState] = await self.service.wallet_state_manager.wallet_node.get_coin_state(
[uncurried_nft.singleton_launcher_id], peer=peer
)
if launcher_coin is None or len(launcher_coin) < 1 or launcher_coin[0].spent_height is None:
return {
"success": False,
"error": f"Launcher coin record 0x{uncurried_nft.singleton_launcher_id.hex()} not found",
}
nft_info: NFTInfo = nft_puzzles.get_nft_info_from_puzzle(
NFTCoinInfo(
uncurried_nft.singleton_launcher_id,
coin_state.coin,
None,
full_puzzle,
launcher_coin[0].spent_height,
)
)
except Exception as e:
return {"success": False, "error": f"The coin is not a NFT. {e}"}
else:
return {"success": True, "nft_info": nft_info}
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"]
# Note metadata updater can only add one uri for one field per spend.
# If you want to add multiple uris for one field, you need to spend multiple times.
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"]))
uri = request["uri"]
key = request["key"]
nft_coin_id = request["nft_coin_id"]
if nft_coin_id.startswith(NFT_HRP):
nft_coin_id = decode_puzzle_hash(nft_coin_id)
else:
nft_coin_id = bytes32.from_hexstr(nft_coin_id)
nft_coin_info = nft_wallet.get_nft_coin_by_id(nft_coin_id)
fee = uint64(request.get("fee", 0))
spend_bundle = await nft_wallet.update_metadata(nft_coin_info, uri, fee=fee)
spend_bundle = await nft_wallet.update_metadata(nft_coin_info, key, 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}")
+58 -11
View File
@@ -576,35 +576,72 @@ class WalletRpcClient(RpcClient):
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):
async def create_new_nft_wallet(self, did_id, name=None):
request: Dict[str, Any] = {
"wallet_type": "nft_wallet",
"did_wallet_id": did_wallet_id,
"did_id": did_id,
"name": name,
}
response = await self.fetch("create_new_wallet", request)
return response
async def mint_nft(self, wallet_id, artist_address, hash, uris, fee):
async def mint_nft(
self,
wallet_id,
royalty_address,
target_address,
hash,
uris,
meta_hash="",
meta_uris=[],
license_hash="",
license_uris=[],
series_total=1,
series_number=1,
fee=0,
royalty_percentage=0,
did_id=None,
):
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"artist_address": artist_address,
"royalty_address": royalty_address,
"target_address": target_address,
"hash": hash,
"uris": uris,
"meta_hash": meta_hash,
"meta_uris": meta_uris,
"license_hash": license_hash,
"license_uris": license_uris,
"series_number": series_number,
"series_total": series_total,
"royalty_percentage": royalty_percentage,
"did_id": did_id,
"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):
async def add_uri_to_nft(self, wallet_id, nft_coin_id, key, uri, fee):
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"nft_coin_id": nft_coin_id,
"target_address": artist_address,
"uri": uri,
"key": key,
"fee": fee,
}
response = await self.fetch("nft_add_uri", request)
return response
async def get_nft_info(self, coin_id: str, latest: bool = True):
request: Dict[str, Any] = {"coin_id": coin_id, "latest": latest}
response = await self.fetch("nft_get_info", request)
return response
async def transfer_nft(self, wallet_id, nft_coin_id, target_address, fee):
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"nft_coin_id": nft_coin_id,
"target_address": target_address,
"fee": fee,
}
response = await self.fetch("nft_transfer_nft", request)
@@ -615,6 +652,16 @@ class WalletRpcClient(RpcClient):
response = await self.fetch("nft_get_nfts", request)
return response
async def set_nft_did(self, wallet_id, did_id, nft_coin_id, fee):
request: Dict[str, Any] = {"wallet_id": wallet_id, "did_id": did_id, "nft_coin_id": nft_coin_id, "fee": fee}
response = await self.fetch("nft_set_nft_did", request)
return response
async def get_nft_wallet_did(self, wallet_id):
request: Dict[str, Any] = {"wallet_id": wallet_id}
response = await self.fetch("nft_get_wallet_did", request)
return response
# DataLayer
async def create_new_dl(self, root: bytes32, fee: uint64) -> Tuple[List[TransactionRecord], bytes32]:
request = {"root": root.hex(), "fee": fee}
+9 -3
View File
@@ -30,7 +30,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.peer_info import PeerInfo
from chia.util.errors import Err, ProtocolError
from chia.util.ints import uint16
from chia.util.network import is_in_network, is_localhost
from chia.util.network import is_in_network, is_localhost, select_port
from chia.util.ssl_check import verify_ssl_certs_and_keys
max_message_size = 50 * 1024 * 1024 # 50MB
@@ -268,13 +268,19 @@ class ChiaServer:
# this port from the socket itself and update self._port.
self.site = web.TCPSite(
self.runner,
host="0.0.0.0",
host="", # should listen to both IPv4 and IPv6 on a dual-stack system
port=int(self._port),
shutdown_timeout=3,
ssl_context=ssl_context,
)
await self.site.start()
self._port = self.runner.addresses[0][1]
#
# On a dual-stack system, we want to get the (first) IPv4 port unless
# prefer_ipv6 is set in which case we use the IPv6 port
#
if self._port == 0:
self._port = select_port(self.root_path, self.runner.addresses)
self.log.info(f"Started listening on port: {self._port}")
async def incoming_connection(self, request):
+3 -2
View File
@@ -3,10 +3,11 @@ import logging
import os
import shutil
from aiofiles import tempfile # type: ignore
from pathlib import Path
from typing import Union
from aiofiles import tempfile
from typing_extensions import Literal
log = logging.getLogger(__name__)
@@ -66,7 +67,7 @@ async def write_file_async(file_path: Path, data: Union[str, bytes], *, file_mod
# Create the parent directory if necessary
os.makedirs(file_path.parent, mode=dir_mode, exist_ok=True)
mode: str = "w+" if type(data) == str else "w+b"
mode: Literal["w+", "w+b"] = "w+" if type(data) == str else "w+b"
temp_file_path: Path
async with tempfile.NamedTemporaryFile(dir=file_path.parent, mode=mode, delete=False) as f:
temp_file_path = f.name
+20
View File
@@ -1,9 +1,11 @@
import socket
from pathlib import Path
from ipaddress import ip_address, IPv4Network, IPv6Network
from typing import Iterable, List, Tuple, Union, Any, Optional, Dict
from chia.server.outbound_message import NodeType
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.peer_info import PeerInfo
from chia.util.config import load_config
from chia.util.ints import uint16
@@ -84,3 +86,21 @@ def is_trusted_inner(peer_host: str, peer_node_id: bytes32, trusted_peers: Dict,
return False
return True
def select_port(root_path: Path, addresses: List[Any]) -> uint16:
global_config = load_config(root_path, "config.yaml")
prefer_ipv6 = global_config.get("prefer_ipv6", False)
selected_port: uint16
for address_string, port, *_ in addresses:
address = ip_address(address_string)
if address.version == 6 and prefer_ipv6:
selected_port = port
break
elif address.version == 4 and not prefer_ipv6:
selected_port = port
break
else:
selected_port = addresses[0][1] # no matches, just use the first one in the list
return selected_port
+23 -1
View File
@@ -23,6 +23,8 @@ class CATOuterPuzzle:
_asset_id: Any
_construct: Any
_solve: Any
_get_inner_puzzle: Any
_get_inner_solution: Any
def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
matched, curried_args = match_cat_puzzle(puzzle)
@@ -39,6 +41,26 @@ class CATOuterPuzzle:
else:
return None
def get_inner_puzzle(self, constructor: PuzzleInfo, puzzle_reveal: Program) -> Optional[Program]:
matched, curried_args = match_cat_puzzle(puzzle_reveal)
if matched:
_, _, inner_puzzle = curried_args
if constructor.also() is not None:
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle)
return deep_inner_puzzle
else:
return inner_puzzle
else:
raise ValueError("This driver is not for the specified puzzle reveal")
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
my_inner_solution: Program = solution.first()
if constructor.also():
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution)
return deep_inner_solution
else:
return my_inner_solution
def asset_id(self, constructor: PuzzleInfo) -> Optional[bytes32]:
return bytes32(constructor["tail"])
@@ -73,7 +95,7 @@ class CATOuterPuzzle:
parent_coin: Coin = parent_spend.coin
if constructor.also() is not None:
puzzle = self._construct(constructor.also(), puzzle)
solution = self._solve(constructor.also(), solver, puzzle, solution)
solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution)
matched, curried_args = match_cat_puzzle(parent_spend.puzzle_reveal.to_program())
assert matched
_, _, parent_inner_puzzle = curried_args
+2
View File
@@ -8,6 +8,8 @@ from chia.wallet.lineage_proof import LineageProof
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.coin import Coin
DID_HRP = "did:chia:"
@streamable
@dataclass(frozen=True)
+109 -75
View File
@@ -1,3 +1,4 @@
import dataclasses
import logging
import time
import json
@@ -94,7 +95,7 @@ class DIDWallet:
)
info_as_string = json.dumps(self.did_info.to_json_dict())
self.wallet_info = await wallet_state_manager.user_store.create_wallet(
name, WalletType.DISTRIBUTED_ID.value, info_as_string
name, WalletType.DECENTRALIZED_ID.value, info_as_string
)
if self.wallet_info is None:
raise ValueError("Internal Error")
@@ -105,7 +106,7 @@ class DIDWallet:
raise ValueError("Not enough balance")
try:
spend_bundle = await self.generate_new_decentralised_id(uint64(amount))
spend_bundle = await self.generate_new_decentralised_id(amount, fee)
except Exception:
await wallet_state_manager.user_store.delete_wallet(self.id(), False)
raise
@@ -114,50 +115,7 @@ class DIDWallet:
await wallet_state_manager.user_store.delete_wallet(self.id(), False)
raise ValueError("Failed to create spend.")
await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id)
assert self.did_info.origin_coin is not None
assert self.did_info.current_inner is not None
did_puzzle_hash = did_wallet_puzzles.create_fullpuz(
self.did_info.current_inner, self.did_info.origin_coin.name()
).get_tree_hash()
did_record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
to_puzzle_hash=did_puzzle_hash,
amount=uint64(amount),
fee_amount=fee,
confirmed=False,
sent=uint32(10),
spend_bundle=None,
additions=spend_bundle.additions(),
removals=spend_bundle.removals(),
wallet_id=self.id(),
sent_to=[],
trade_id=None,
type=uint32(TransactionType.INCOMING_TX.value),
name=bytes32(token_bytes()),
memos=[],
)
regular_record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
to_puzzle_hash=did_puzzle_hash,
amount=uint64(amount),
fee_amount=fee,
confirmed=False,
sent=uint32(0),
spend_bundle=spend_bundle,
additions=spend_bundle.additions(),
removals=spend_bundle.removals(),
wallet_id=self.wallet_state_manager.main_wallet.id(),
sent_to=[],
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32(token_bytes()),
memos=list(compute_memos(spend_bundle).items()),
)
await self.standard_wallet.push_transaction(regular_record)
await self.standard_wallet.push_transaction(did_record)
return self
@staticmethod
@@ -189,7 +147,7 @@ class DIDWallet:
self.check_existed_did()
info_as_string = json.dumps(self.did_info.to_json_dict())
self.wallet_info = await wallet_state_manager.user_store.create_wallet(
name, WalletType.DISTRIBUTED_ID.value, info_as_string
name, WalletType.DECENTRALIZED_ID.value, info_as_string
)
await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id)
await self.save_info(self.did_info, False)
@@ -258,11 +216,10 @@ class DIDWallet:
info_as_string = json.dumps(self.did_info.to_json_dict())
self.wallet_info = await wallet_state_manager.user_store.create_wallet(
name, WalletType.DISTRIBUTED_ID.value, info_as_string, in_transaction=True
name, WalletType.DECENTRALIZED_ID.value, info_as_string, in_transaction=True
)
await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id)
await self.wallet_state_manager.update_wallet_puzzle_hashes(self.wallet_info.id)
await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id, in_transaction=True)
await self.wallet_state_manager.update_wallet_puzzle_hashes(self.wallet_info.id, in_transaction=True)
await self.load_parent(self.did_info)
self.log.info(f"New DID wallet created {info_as_string}.")
if self.wallet_info is None:
@@ -299,9 +256,9 @@ class DIDWallet:
@classmethod
def type(cls) -> uint8:
return uint8(WalletType.DISTRIBUTED_ID)
return uint8(WalletType.DECENTRALIZED_ID)
def id(self):
def id(self) -> uint32:
return self.wallet_info.id
async def get_confirmed_balance(self, record_list=None) -> uint128:
@@ -388,6 +345,7 @@ class DIDWallet:
# This will be used in the recovery case where we don't have the parent info already
async def coin_added(self, coin: Coin, _: uint32):
"""Notification from wallet state manager that wallet has been received."""
self.log.info(f"DID wallet has been notified that coin was added: {coin.name()}:{coin}")
inner_puzzle = await self.inner_puzzle_for_did_puzzle(coin.puzzle_hash)
if self.did_info.temp_coin is not None:
@@ -461,11 +419,23 @@ class DIDWallet:
"""
# full_puz = did_wallet_puzzles.create_fullpuz(innerpuz, origin.name())
# All additions in this block here:
new_puzhash = await self.get_new_did_inner_hash()
new_pubkey = bytes((await self.wallet_state_manager.get_unused_derivation_record(self.wallet_info.id)).pubkey)
new_pubkey = bytes(
(
await self.wallet_state_manager.get_unused_derivation_record(self.wallet_info.id, in_transaction=True)
).pubkey
)
new_puzhash = puzzle_for_pk(new_pubkey).get_tree_hash()
parent_info = None
assert did_info.origin_coin is not None
assert did_info.current_inner is not None
new_did_inner_puzhash = did_wallet_puzzles.get_inner_puzhash_by_p2(
new_puzhash,
did_info.backup_ids,
did_info.num_of_backup_ids_needed,
did_info.origin_coin.name(),
did_wallet_puzzles.metadata_to_program(json.loads(self.did_info.metadata)),
)
node = self.wallet_state_manager.wallet_node.get_full_node_peer()
children = await self.wallet_state_manager.wallet_node.fetch_children(node, did_info.origin_coin.name())
while True:
@@ -481,8 +451,7 @@ class DIDWallet:
did_info.current_inner.get_tree_hash(),
coin.amount,
)
await self.add_parent(coin.name(), future_parent, False)
await self.add_parent(coin.name(), future_parent, True)
if children_state.spent_height != children_state.created_height:
did_info = DIDInfo(
did_info.origin_coin,
@@ -491,12 +460,13 @@ class DIDWallet:
self.did_info.parent_info,
did_info.current_inner,
coin,
new_puzhash,
new_did_inner_puzhash,
new_pubkey,
False,
did_info.metadata,
)
await self.save_info(did_info, False)
await self.save_info(did_info, True)
assert children_state.created_height
puzzle_solution_request = wallet_protocol.RequestPuzzleSolution(
coin.parent_coin_info, children_state.created_height
@@ -514,9 +484,23 @@ class DIDWallet:
parent_innerpuz.get_tree_hash(),
parent_state.coin.amount,
)
await self.add_parent(coin.parent_coin_info, parent_info, False)
await self.add_parent(coin.parent_coin_info, parent_info, True)
assert parent_info is not None
async def create_tandem_xch_tx(
self, fee: uint64, announcement_to_assert: Optional[Announcement] = None
) -> TransactionRecord:
chia_coins = await self.standard_wallet.select_coins(fee)
chia_tx = await self.standard_wallet.generate_signed_transaction(
uint64(0),
(await self.standard_wallet.get_new_puzzlehash()),
fee=fee,
coins=chia_coins,
coin_announcements_to_consume={announcement_to_assert} if announcement_to_assert is not None else None,
)
assert chia_tx.spend_bundle is not None
return chia_tx
def puzzle_for_pk(self, pubkey: G1Element) -> Program:
if self.did_info.origin_coin is not None:
innerpuz = did_wallet_puzzles.create_innerpuz(
@@ -552,7 +536,7 @@ class DIDWallet:
async def get_name(self):
return self.wallet_info.name
async def create_update_spend(self):
async def create_update_spend(self, fee: uint64 = uint64(0)):
assert self.did_info.current_inner is not None
assert self.did_info.origin_coin is not None
coins = await self.select_coins(1)
@@ -561,7 +545,8 @@ class DIDWallet:
new_puzhash = await self.get_new_did_inner_hash()
# innerpuz solution is (mode, p2_solution)
p2_solution = self.standard_wallet.make_solution(
primaries=[{"puzzlehash": new_puzhash, "amount": uint64(coin.amount), "memos": [new_puzhash]}]
primaries=[{"puzzlehash": new_puzhash, "amount": uint64(coin.amount), "memos": [new_puzhash]}],
coin_announcements={coin.name()},
)
innersol: Program = Program.to([1, p2_solution])
# full solution is (corehash parent_info my_amount innerpuz_reveal solution)
@@ -587,7 +572,16 @@ class DIDWallet:
list_of_coinspends = [CoinSpend(coin, full_puzzle, fullsol)]
unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element())
spend_bundle = await self.sign(unsigned_spend_bundle)
if fee > 0:
announcement_to_make = coin.name()
chia_tx = await self.create_tandem_xch_tx(fee, Announcement(coin.name(), announcement_to_make))
else:
announcement_to_make = None
chia_tx = None
if chia_tx is not None and chia_tx.spend_bundle is not None:
spend_bundle = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle])
chia_tx = dataclasses.replace(chia_tx, spend_bundle=None)
await self.wallet_state_manager.add_pending_transaction(chia_tx)
did_record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
@@ -606,7 +600,8 @@ class DIDWallet:
name=bytes32(token_bytes()),
memos=list(compute_memos(spend_bundle).items()),
)
await self.standard_wallet.push_transaction(did_record)
await self.wallet_state_manager.add_pending_transaction(did_record)
return spend_bundle
async def transfer_did(self, new_puzhash: bytes32, fee: uint64, with_recovery: bool) -> TransactionRecord:
@@ -641,7 +636,8 @@ class DIDWallet:
"amount": uint64(coin.amount),
"memos": [new_puzhash],
}
]
],
coin_announcements={coin.name()},
)
# Need to include backup list reveal here, even we are don't recover
# innerpuz solution is
@@ -671,7 +667,15 @@ class DIDWallet:
list_of_coinspends = [CoinSpend(coin, full_puzzle, fullsol)]
unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element())
spend_bundle = await self.sign(unsigned_spend_bundle)
if fee > 0:
announcement_to_make = coin.name()
chia_tx = await self.create_tandem_xch_tx(fee, Announcement(coin.name(), announcement_to_make))
else:
chia_tx = None
if chia_tx is not None and chia_tx.spend_bundle is not None:
spend_bundle = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle])
chia_tx = dataclasses.replace(chia_tx, spend_bundle=None)
await self.wallet_state_manager.add_pending_transaction(chia_tx)
did_record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
@@ -690,7 +694,7 @@ class DIDWallet:
name=bytes32(token_bytes()),
memos=list(compute_memos(spend_bundle).items()),
)
await self.standard_wallet.push_transaction(did_record)
await self.wallet_state_manager.add_pending_transaction(did_record)
return did_record
# The message spend can tests\wallet\rpc\test_wallet_rpc.py send messages and also change your innerpuz
@@ -793,7 +797,7 @@ class DIDWallet:
name=bytes32(token_bytes()),
memos=list(compute_memos(spend_bundle).items()),
)
await self.standard_wallet.push_transaction(did_record)
await self.wallet_state_manager.add_pending_transaction(did_record)
return spend_bundle
# Pushes a SpendBundle to create a message coin on the blockchain
@@ -869,7 +873,7 @@ class DIDWallet:
)
attest_str: str = f"{self.get_my_DID()}:{bytes(message_spend_bundle).hex()}:{coin.parent_coin_info.hex()}:"
attest_str += f"{self.did_info.current_inner.get_tree_hash().hex()}:{coin.amount}"
await self.standard_wallet.push_transaction(did_record)
await self.wallet_state_manager.add_pending_transaction(did_record)
return message_spend_bundle, attest_str
async def get_info_for_recovery(self) -> Optional[Tuple[bytes32, bytes32, uint64]]:
@@ -995,7 +999,7 @@ class DIDWallet:
name=bytes32(token_bytes()),
memos=list(compute_memos(spend_bundle).items()),
)
await self.standard_wallet.push_transaction(did_record)
await self.wallet_state_manager.add_pending_transaction(did_record)
new_did_info = DIDInfo(
self.did_info.origin_coin,
self.did_info.backup_ids,
@@ -1112,12 +1116,12 @@ class DIDWallet:
agg_sig = AugSchemeMPL.aggregate(sigs)
return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)])
async def generate_new_decentralised_id(self, amount: uint64) -> Optional[SpendBundle]:
async def generate_new_decentralised_id(self, amount: uint64, fee: uint64 = uint64(0)) -> Optional[SpendBundle]:
"""
This must be called under the wallet state manager lock
"""
coins = await self.standard_wallet.select_coins(amount)
coins = await self.standard_wallet.select_coins(uint64(amount + fee))
if coins is None:
return None
@@ -1135,7 +1139,7 @@ class DIDWallet:
announcement_set.add(Announcement(launcher_coin.name(), announcement_message))
tx_record: Optional[TransactionRecord] = await self.standard_wallet.generate_signed_transaction(
amount, genesis_launcher_puz.get_tree_hash(), uint64(0), origin.name(), coins, None, False, announcement_set
amount, genesis_launcher_puz.get_tree_hash(), fee, origin.name(), coins, None, False, announcement_set
)
genesis_launcher_solution = Program.to([did_puzzle_hash, amount, bytes(0x80)])
@@ -1175,17 +1179,47 @@ class DIDWallet:
await self.save_info(did_info, False)
eve_spend = await self.generate_eve_spend(eve_coin, did_full_puz, did_inner)
full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend, launcher_sb])
assert self.did_info.origin_coin is not None
assert self.did_info.current_inner is not None
did_puzzle_hash = did_wallet_puzzles.create_fullpuz(
self.did_info.current_inner, self.did_info.origin_coin.name()
).get_tree_hash()
did_record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
to_puzzle_hash=did_puzzle_hash,
amount=uint64(amount),
fee_amount=fee,
confirmed=False,
sent=uint32(10),
spend_bundle=full_spend,
additions=full_spend.additions(),
removals=full_spend.removals(),
wallet_id=self.id(),
sent_to=[],
trade_id=None,
type=uint32(TransactionType.INCOMING_TX.value),
name=bytes32(token_bytes()),
memos=[],
)
regular_record = dataclasses.replace(tx_record, spend_bundle=None)
await self.wallet_state_manager.add_pending_transaction(regular_record)
await self.wallet_state_manager.add_pending_transaction(did_record)
return full_spend
async def generate_eve_spend(self, coin: Coin, full_puzzle: Program, innerpuz: Program):
assert self.did_info.origin_coin is not None
uncurried = did_wallet_puzzles.uncurry_innerpuz(innerpuz)
assert uncurried is not None
p2_puzzle = uncurried[0]
# innerpuz solution is (mode p2_solution)
p2_solution = self.standard_wallet.make_solution(
primaries=[
{
"puzzlehash": innerpuz.get_tree_hash(),
"amount": uint64(coin.amount),
"memos": [innerpuz.get_tree_hash()],
"memos": [p2_puzzle.get_tree_hash()],
}
]
)
@@ -1285,7 +1319,7 @@ class DIDWallet:
"""
max_num = 0
for wallet in self.wallet_state_manager.wallets.values():
if wallet.type() == WalletType.DISTRIBUTED_ID:
if wallet.type() == WalletType.DECENTRALIZED_ID:
matched = re.search(r"^Profile (\d+)$", wallet.wallet_info.name)
if matched and int(matched.group(1)) > max_num:
max_num = int(matched.group(1))
@@ -1298,7 +1332,7 @@ class DIDWallet:
"""
for wallet in self.wallet_state_manager.wallets.values():
if (
wallet.type() == WalletType.DISTRIBUTED_ID
wallet.type() == WalletType.DECENTRALIZED_ID
and self.did_info.origin_coin.name() == wallet.did_info.origin_coin.name()
):
self.log.warning(f"DID {self.did_info.origin_coin} already existed, ignore the wallet creation.")
+1 -2
View File
@@ -1,4 +1,3 @@
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, Iterator, Dict
@@ -205,7 +204,7 @@ def metadata_to_program(metadata: Dict) -> Program:
"""
kv_list = []
for key, value in metadata.items():
kv_list.append((assemble(key), assemble(value)))
kv_list.append((key, value))
return Program.to(kv_list)
@@ -0,0 +1,94 @@
from dataclasses import dataclass
from typing import Any, List, Optional, Tuple
from clvm_tools.binutils import disassemble
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.puzzle_drivers import PuzzleInfo, Solver
from chia.wallet.puzzles.load_clvm import load_clvm
NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm")
NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
def match_metadata_layer_puzzle(puzzle: Program) -> Tuple[bool, List[Program]]:
mod, meta_args = puzzle.uncurry()
if mod == NFT_STATE_LAYER_MOD:
return True, list(meta_args.as_iter())
return False, []
def puzzle_for_metadata_layer(metadata: Program, updater_hash: bytes32, inner_puzzle: Program) -> Program:
return NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, updater_hash, inner_puzzle)
def solution_for_metadata_layer(amount: uint64, inner_solution: Program) -> Program:
return Program.to([inner_solution, amount]) # type: ignore
@dataclass(frozen=True)
class MetadataOuterPuzzle:
_match: Any
_asset_id: Any
_construct: Any
_solve: Any
_get_inner_puzzle: Any
_get_inner_solution: Any
def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
matched, curried_args = match_metadata_layer_puzzle(puzzle)
if matched:
_, metadata, updater_hash, inner_puzzle = curried_args
constructor_dict = {
"type": "metadata",
"metadata": disassemble(metadata), # type: ignore
"updater_hash": "0x" + updater_hash.as_python().hex(),
}
next_constructor = self._match(inner_puzzle)
if next_constructor is not None:
constructor_dict["also"] = next_constructor.info
return PuzzleInfo(constructor_dict)
else:
return None
return None # Uncomment above when match_metadata_layer_puzzle works
def asset_id(self, constructor: PuzzleInfo) -> Optional[bytes32]:
return bytes32(constructor["updater_hash"])
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
if constructor.also() is not None:
inner_puzzle = self._construct(constructor.also(), inner_puzzle)
return puzzle_for_metadata_layer(constructor["metadata"], constructor["updater_hash"], inner_puzzle)
def get_inner_puzzle(self, constructor: PuzzleInfo, puzzle_reveal: Program) -> Optional[Program]:
matched, curried_args = match_metadata_layer_puzzle(puzzle_reveal)
if matched:
_, _, _, inner_puzzle = curried_args
if constructor.also() is not None:
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle)
return deep_inner_puzzle
else:
return inner_puzzle
else:
raise ValueError("This driver is not for the specified puzzle reveal")
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
my_inner_solution: Program = solution.first()
if constructor.also():
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution)
return deep_inner_solution
else:
return my_inner_solution
def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
coin_bytes: bytes = solver["coin"]
coin: Coin = Coin(bytes32(coin_bytes[0:32]), bytes32(coin_bytes[32:64]), uint64.from_bytes(coin_bytes[64:72]))
if constructor.also() is not None:
inner_solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution)
return solution_for_metadata_layer(
coin.amount,
inner_solution,
)
+59 -14
View File
@@ -1,8 +1,19 @@
from dataclasses import dataclass
from typing import List
from typing import List, Optional
from chia.util.ints import uint64
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 uint16, uint32, uint64
from chia.util.streamable import Streamable, streamable
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.puzzles.load_clvm import load_clvm
LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
IN_TRANSACTION_STATUS = "IN_TRANSACTION"
DEFAULT_STATUS = "DEFAULT"
NFT_HRP = "nft"
@streamable
@@ -10,41 +21,75 @@ from chia.util.streamable import Streamable, streamable
class NFTInfo(Streamable):
"""NFT Info for displaying NFT on the UI"""
launcher_id: str
launcher_id: bytes32
"""Launcher coin ID"""
nft_coin_id: str
nft_coin_id: bytes32
"""Current NFT coin ID"""
did_owner: str
owner_did: Optional[bytes32]
"""Owner DID"""
royalty: uint64
royalty_percentage: Optional[uint16]
"""Percentage of the transaction fee paid to the author, e.g. 1000 = 1%"""
royalty_puzzle_hash: Optional[bytes32]
"""Puzzle hash where royalty will be sent to"""
data_uris: List[str]
""" A list of content URIs"""
data_hash: str
data_hash: bytes
"""Hash of the content"""
metadata_uris: List[str]
"""A list of metadata URIs"""
metadata_hash: str
metadata_hash: bytes
"""Hash of the metadata"""
license_uris: List[str]
"""A list of license URIs"""
license_hash: str
license_hash: bytes
"""Hash of the license"""
version: str
"""Current NFT version"""
edition_count: uint64
series_total: uint64
"""How many NFTs in the current series"""
edition_number: uint64
series_number: uint64
"""Number of the current NFT in the series"""
updater_puzhash: bytes32
"""Puzzle hash of the metadata updater in hex"""
chain_info: str
"""Information saved on the chain in hex"""
mint_height: uint32
"""Block height of the NFT minting"""
supports_did: bool
"""If the inner puzzle supports DID"""
pending_transaction: bool = False
"""Indicate if the NFT is pending for a transaction"""
launcher_puzhash: bytes32 = LAUNCHER_PUZZLE.get_tree_hash()
"""Puzzle hash of the singleton launcher in hex"""
@streamable
@dataclass(frozen=True)
class NFTCoinInfo(Streamable):
nft_id: bytes32
coin: Coin
lineage_proof: Optional[LineageProof]
full_puzzle: Program
mint_height: uint32
pending_transaction: bool = False
@streamable
@dataclass(frozen=True)
class NFTWalletInfo(Streamable):
did_id: Optional[bytes32] = None
+230 -24
View File
@@ -1,12 +1,16 @@
import logging
from typing import Any, Dict, List, Optional, Tuple
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from clvm.casts import int_from_bytes
from clvm_tools.binutils import disassemble
from chia.types.blockchain_format.program import Program, SerializedProgram
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.util.ints import uint16, uint64
from chia.wallet.nft_wallet.nft_info import NFTCoinInfo, NFTInfo
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 solution_for_conditions
log = logging.getLogger(__name__)
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
@@ -15,9 +19,10 @@ 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")
NFT_OWNERSHIP_LAYER = load_clvm("nft_ownership_layer.clvm")
NFT_TRANSFER_PROGRAM_DEFAULT = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties.clvm")
STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm")
def create_nft_layer_puzzle_with_curry_params(
@@ -36,6 +41,13 @@ def create_nft_layer_puzzle_with_curry_params(
metadata,
metadata_updater_hash,
)
log.debug(
"Currying with: %s %s %s %s",
NFT_STATE_LAYER_MOD_HASH,
inner_puzzle.get_tree_hash(),
metadata_updater_hash,
metadata.get_tree_hash(),
)
return NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, metadata_updater_hash, inner_puzzle)
@@ -70,32 +82,226 @@ def create_full_puzzle(
return full_puzzle
def get_nft_info_from_puzzle(puzzle: Program, nft_coin: Coin) -> NFTInfo:
def get_nft_info_from_puzzle(nft_coin_info: NFTCoinInfo) -> NFTInfo:
"""
Extract NFT info from a full puzzle
:param puzzle: NFT full puzzle
:param nft_coin: NFT coin
:param nft_coin_info NFTCoinInfo in local database
:return: NFTInfo
"""
# TODO Update this method after the NFT code finalized
uncurried_nft: UncurriedNFT = UncurriedNFT.uncurry(puzzle)
data_uris = []
uncurried_nft: UncurriedNFT = UncurriedNFT.uncurry(nft_coin_info.full_puzzle)
data_uris: List[str] = []
for uri in uncurried_nft.data_uris.as_python():
data_uris.append(str(uri, "utf-8"))
meta_uris: List[str] = []
for uri in uncurried_nft.meta_uris.as_python():
meta_uris.append(str(uri, "utf-8"))
license_uris: List[str] = []
for uri in uncurried_nft.license_uris.as_python():
license_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()),
uncurried_nft.singleton_launcher_id,
nft_coin_info.coin.name(),
uncurried_nft.owner_did,
uncurried_nft.trade_price_percentage,
uncurried_nft.royalty_address,
data_uris,
uncurried_nft.data_hash.as_python().hex().upper(),
[],
"",
[],
"",
"NFT0",
uint64(1),
uint64(1),
uncurried_nft.data_hash.as_python(),
meta_uris,
uncurried_nft.meta_hash.as_python(),
license_uris,
uncurried_nft.license_hash.as_python(),
uint64(uncurried_nft.series_total.as_int()),
uint64(uncurried_nft.series_number.as_int()),
uncurried_nft.metadata_updater_hash.as_python(),
disassemble(uncurried_nft.metadata),
nft_coin_info.mint_height,
uncurried_nft.supports_did,
nft_coin_info.pending_transaction,
)
return nft_info
def metadata_to_program(metadata: Dict[bytes, Any]) -> 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((key, value))
program: Program = Program.to(kv_list)
return program
def program_to_metadata(program: Program) -> Dict[bytes, Any]:
"""
Convert a program to a metadata dict
:param program: Chialisp program contains the metadata
:return: Metadata dict
"""
metadata = {}
for kv_pair in program.as_iter():
metadata[kv_pair.first().as_atom()] = kv_pair.rest().as_python()
return metadata
def prepend_value(key: bytes, value: Program, metadata: Dict[bytes, Any]) -> None:
"""
Prepend a value to a list in the metadata
:param key: Key of the field
:param value: Value want to add
:param metadata: Metadata
:return:
"""
if value != Program.to(0):
if metadata[key] == b"":
metadata[key] = [value.as_python()]
else:
metadata[key].insert(0, value.as_python())
def update_metadata(metadata: Program, update_condition: Program) -> Program:
"""
Apply conditions of metadata updater to the previous metadata
:param metadata: Previous metadata
:param update_condition: Update metadata conditions
:return: Updated metadata
"""
new_metadata: Dict[bytes, Any] = program_to_metadata(metadata)
uri: Program = update_condition.rest().rest().first()
prepend_value(uri.first().as_python(), uri.rest(), new_metadata)
return metadata_to_program(new_metadata)
def construct_ownership_layer(
current_owner: Optional[bytes32],
transfer_program: Program,
inner_puzzle: Program,
) -> Program:
return NFT_OWNERSHIP_LAYER.curry(
NFT_OWNERSHIP_LAYER.get_tree_hash(),
current_owner,
transfer_program,
inner_puzzle,
)
def create_ownership_layer_puzzle(
nft_id: bytes32,
did_id: bytes,
p2_puzzle: Program,
percentage: uint16,
royalty_puzzle_hash: Optional[bytes32] = None,
) -> Program:
log.debug(
"Creating ownership layer puzzle with NFT_ID: %s DID_ID: %s Royalty_Percentage: %d P2_puzzle: %s",
nft_id.hex(),
did_id,
percentage,
p2_puzzle,
)
singleton_struct = Program.to((SINGLETON_MOD_HASH, (nft_id, LAUNCHER_PUZZLE_HASH)))
if not royalty_puzzle_hash:
royalty_puzzle_hash = p2_puzzle.get_tree_hash()
transfer_program = NFT_TRANSFER_PROGRAM_DEFAULT.curry(
singleton_struct,
royalty_puzzle_hash,
percentage,
)
nft_inner_puzzle = p2_puzzle
nft_ownership_layer_puzzle = construct_ownership_layer(
bytes32(did_id) if did_id else None, transfer_program, nft_inner_puzzle
)
return nft_ownership_layer_puzzle
def create_ownership_layer_transfer_solution(
new_did: bytes,
new_did_inner_hash: bytes,
trade_prices_list: List[List[int]],
new_puzhash: bytes32,
) -> Program:
log.debug(
"Creating a transfer solution with: DID:%s Inner_puzhash:%s trade_price:%s puzhash:%s",
new_did.hex(),
new_did_inner_hash.hex(),
str(trade_prices_list),
new_puzhash.hex(),
)
condition_list = [
[
51,
new_puzhash,
1,
[new_puzhash],
],
[-10, new_did, trade_prices_list, new_did_inner_hash],
]
log.debug("Condition list raw: %r", condition_list)
solution = Program.to(
[
[solution_for_conditions(condition_list)],
]
)
log.debug("Generated transfer solution: %s", solution)
return solution
def get_metadata_and_phs(unft: UncurriedNFT, solution: SerializedProgram) -> Tuple[Program, bytes32]:
conditions = unft.p2_puzzle.run(unft.get_innermost_solution(solution.to_program()))
metadata = unft.metadata
puzhash_for_derivation: Optional[bytes32] = None
for condition in conditions.as_iter():
if condition.list_len() < 2:
# invalid condition
continue
condition_code = condition.first().as_int()
log.debug("Checking condition code: %r", condition_code)
if condition_code == -24:
# metadata update
metadata = update_metadata(metadata, condition)
metadata = Program.to(metadata)
elif condition_code == 51 and int_from_bytes(condition.rest().rest().first().atom) == 1:
# destination puzhash
if puzhash_for_derivation is not None:
# ignore duplicated create coin conditions
continue
memo = bytes32(condition.as_python()[-1][0])
puzhash_for_derivation = memo
log.debug("Got back puzhash from solution: %s", puzhash_for_derivation)
assert puzhash_for_derivation
return metadata, puzhash_for_derivation
def recurry_nft_puzzle(unft: UncurriedNFT, solution: Program, new_inner_puzzle: Program) -> Program:
log.debug("Generating NFT puzzle with ownership support: %s", disassemble(solution))
conditions = unft.p2_puzzle.run(unft.get_innermost_solution(solution))
new_did_id = unft.owner_did
new_puzhash = None
for condition in conditions.as_iter():
if condition.first().as_int() == -10:
# this is the change owner magic condition
new_did_id = condition.at("rf").atom
elif condition.first().as_int() == 51:
new_puzhash = condition.at("rf").atom
# assert new_puzhash and new_did_id
log.debug(f"Found NFT puzzle details: {new_did_id} {new_puzhash}")
assert unft.transfer_program
new_ownership_puzzle = construct_ownership_layer(new_did_id, unft.transfer_program, new_inner_puzzle)
return new_ownership_puzzle
def get_new_owner_did(unft: UncurriedNFT, solution: Program) -> Optional[bytes32]:
conditions = unft.p2_puzzle.run(unft.get_innermost_solution(solution))
new_did_id = None
for condition in conditions.as_iter():
if condition.first().as_int() == -10:
# this is the change owner magic condition
new_did_id = condition.at("rf").atom
return new_did_id
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,96 @@
from dataclasses import dataclass
from typing import Any, List, Optional, Tuple, Union
from clvm_tools.binutils import disassemble
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
from chia.wallet.puzzles.load_clvm import load_clvm
OWNERSHIP_LAYER_MOD = load_clvm("nft_ownership_layer.clvm")
def match_ownership_layer_puzzle(puzzle: Program) -> Tuple[bool, List[Program]]:
mod, args = puzzle.uncurry()
if mod == OWNERSHIP_LAYER_MOD:
return True, list(args.as_iter())
return False, []
def puzzle_for_ownership_layer(
current_owner: Union[Program, bytes], transfer_program: Program, inner_puzzle: Program
) -> Program:
return OWNERSHIP_LAYER_MOD.curry(OWNERSHIP_LAYER_MOD.get_tree_hash(), current_owner, transfer_program, inner_puzzle)
def solution_for_ownership_layer(inner_solution: Program) -> Program:
return Program.to([inner_solution]) # type: ignore
@dataclass(frozen=True)
class OwnershipOuterPuzzle:
_match: Any
_asset_id: Any
_construct: Any
_solve: Any
_get_inner_puzzle: Any
_get_inner_solution: Any
def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
matched, curried_args = match_ownership_layer_puzzle(puzzle)
if matched:
_, current_owner, transfer_program, inner_puzzle = curried_args
owner_bytes: bytes = current_owner.as_python()
tp_match: Optional[PuzzleInfo] = self._match(transfer_program)
constructor_dict = {
"type": "ownership",
"owner": "()" if owner_bytes == b"" else "0x" + owner_bytes.hex(),
"transfer_program": (
disassemble(transfer_program) if tp_match is None else tp_match.info # type: ignore
),
}
next_constructor = self._match(inner_puzzle)
if next_constructor is not None:
constructor_dict["also"] = next_constructor.info
return PuzzleInfo(constructor_dict)
else:
return None
def asset_id(self, constructor: PuzzleInfo) -> Optional[bytes32]:
return None
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
if constructor.also() is not None:
inner_puzzle = self._construct(constructor.also(), inner_puzzle)
transfer_program_info: Union[PuzzleInfo, Program] = constructor["transfer_program"]
if isinstance(transfer_program_info, Program):
transfer_program: Program = transfer_program_info
else:
transfer_program = self._construct(transfer_program_info, inner_puzzle)
return puzzle_for_ownership_layer(constructor["owner"], transfer_program, inner_puzzle)
def get_inner_puzzle(self, constructor: PuzzleInfo, puzzle_reveal: Program) -> Optional[Program]:
matched, curried_args = match_ownership_layer_puzzle(puzzle_reveal)
if matched:
_, _, _, inner_puzzle = curried_args
if constructor.also() is not None:
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle)
return deep_inner_puzzle
else:
return inner_puzzle
else:
raise ValueError("This driver is not for the specified puzzle reveal")
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
my_inner_solution: Program = solution.first()
if constructor.also():
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution)
return deep_inner_solution
else:
return my_inner_solution
def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
if constructor.also() is not None:
inner_solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution)
return solution_for_ownership_layer(inner_solution)
@@ -0,0 +1,87 @@
from dataclasses import dataclass
from typing import Any, Optional
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.util.ints import uint64
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
from chia.wallet.puzzles.singleton_top_layer_v1_1 import (
SINGLETON_LAUNCHER_HASH,
match_singleton_puzzle,
puzzle_for_singleton,
solution_for_singleton,
)
@dataclass(frozen=True)
class SingletonOuterPuzzle:
_match: Any
_asset_id: Any
_construct: Any
_solve: Any
_get_inner_puzzle: Any
_get_inner_solution: Any
def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
matched, curried_args = match_singleton_puzzle(puzzle)
if matched:
singleton_struct, inner_puzzle = curried_args
constructor_dict = {
"type": "singleton",
"launcher_id": "0x" + singleton_struct.as_python()[1].hex(),
"launcher_ph": "0x" + singleton_struct.as_python()[2].hex(),
}
next_constructor = self._match(inner_puzzle)
if next_constructor is not None:
constructor_dict["also"] = next_constructor.info
return PuzzleInfo(constructor_dict)
else:
return None
def asset_id(self, constructor: PuzzleInfo) -> Optional[bytes32]:
return bytes32(constructor["launcher_id"])
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
if constructor.also() is not None:
inner_puzzle = self._construct(constructor.also(), inner_puzzle)
launcher_hash = constructor["launcher_ph"] if "launcher_ph" in constructor else SINGLETON_LAUNCHER_HASH
return puzzle_for_singleton(constructor["launcher_id"], inner_puzzle, launcher_hash)
def get_inner_puzzle(self, constructor: PuzzleInfo, puzzle_reveal: Program) -> Optional[Program]:
matched, curried_args = match_singleton_puzzle(puzzle_reveal)
if matched:
_, inner_puzzle = curried_args
if constructor.also() is not None:
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle)
return deep_inner_puzzle
else:
return inner_puzzle
else:
raise ValueError("This driver is not for the specified puzzle reveal")
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
my_inner_solution: Program = solution.at("rrf")
if constructor.also():
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution)
return deep_inner_solution
else:
return my_inner_solution
def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
coin_bytes: bytes = solver["coin"]
coin: Coin = Coin(bytes32(coin_bytes[0:32]), bytes32(coin_bytes[32:64]), uint64.from_bytes(coin_bytes[64:72]))
parent_spend: CoinSpend = CoinSpend.from_bytes(solver["parent_spend"])
parent_coin: Coin = parent_spend.coin
if constructor.also() is not None:
inner_solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution)
matched, curried_args = match_singleton_puzzle(parent_spend.puzzle_reveal.to_program())
assert matched
_, parent_inner_puzzle = curried_args
return solution_for_singleton(
LineageProof(parent_coin.parent_coin_info, parent_inner_puzzle.get_tree_hash(), parent_coin.amount),
coin.amount,
inner_solution,
)
@@ -0,0 +1,78 @@
from dataclasses import dataclass
from typing import Any, List, Optional, Tuple
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint16
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
from chia.wallet.puzzles.load_clvm import load_clvm
from chia.wallet.puzzles.singleton_top_layer_v1_1 import SINGLETON_LAUNCHER_HASH, SINGLETON_MOD_HASH
TRANSFER_PROGRAM_MOD = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties.clvm")
def match_transfer_program_puzzle(puzzle: Program) -> Tuple[bool, List[Program]]:
mod, args = puzzle.uncurry()
if mod == TRANSFER_PROGRAM_MOD:
return True, list(args.as_iter())
return False, []
def puzzle_for_transfer_program(launcher_id: bytes32, royalty_puzzle_hash: bytes32, percentage: uint16) -> Program:
singleton_struct = Program.to((SINGLETON_MOD_HASH, (launcher_id, SINGLETON_LAUNCHER_HASH)))
return TRANSFER_PROGRAM_MOD.curry(
singleton_struct,
royalty_puzzle_hash,
percentage,
)
def solution_for_transfer_program(
conditions: Program,
current_owner: Optional[bytes32],
new_did: bytes32,
new_did_inner_hash: bytes32,
trade_prices_list: Program,
) -> Program:
return Program.to([conditions, current_owner, [new_did, trade_prices_list, new_did_inner_hash]]) # type: ignore
@dataclass(frozen=True)
class TransferProgramPuzzle:
_match: Any
_asset_id: Any
_construct: Any
_solve: Any
_get_inner_puzzle: Any
_get_inner_solution: Any
def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
matched, curried_args = match_transfer_program_puzzle(puzzle)
if matched:
singleton_struct, royalty_puzzle_hash, percentage = curried_args
constructor_dict = {
"type": "royalty transfer program",
"launcher_id": "0x" + singleton_struct.rest().first().as_python().hex(),
"royalty_address": "0x" + royalty_puzzle_hash.as_python().hex(),
"royalty_percentage": str(percentage.as_int()),
}
return PuzzleInfo(constructor_dict)
else:
return None
def asset_id(self, constructor: PuzzleInfo) -> Optional[bytes32]:
return None
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
return puzzle_for_transfer_program(
constructor["launcher_id"], constructor["royalty_address"], constructor["royalty_percentage"]
)
def get_inner_puzzle(self, constructor: PuzzleInfo, puzzle_reveal: Program) -> Optional[Program]:
return None
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
return None
def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
return Program.to(None) # type: ignore
+106 -32
View File
@@ -1,13 +1,18 @@
from __future__ import annotations
import logging
from dataclasses import dataclass
from typing import Type, TypeVar
from typing import Optional, Type, TypeVar
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint16
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")
NFT_MOD = load_clvm("nft_state_layer.clvm")
NFT_OWNERSHIP_LAYER = load_clvm("nft_ownership_layer.clvm")
_T_UncurriedNFT = TypeVar("_T_UncurriedNFT", bound="UncurriedNFT")
@@ -20,7 +25,7 @@ class UncurriedNFT:
This is the only place you need to change after modified the Chialisp curried parameters.
"""
nft_mod_hash: Program
nft_mod_hash: bytes32
"""NFT module hash"""
nft_state_layer: Program
@@ -32,28 +37,12 @@ class UncurriedNFT:
[singleton_mod_hash, singleton_launcher_id, launcher_puzhash]
"""
singleton_mod_hash: Program
singleton_launcher_id: Program
singleton_launcher_id: bytes32
launcher_puzhash: Program
owner_did: Program
"""Owner's DID"""
metdata_updater_hash: Program
metadata_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
@@ -61,10 +50,40 @@ class UncurriedNFT:
"""
data_uris: Program
data_hash: Program
meta_uris: Program
meta_hash: Program
license_uris: Program
license_hash: Program
series_number: Program
series_total: Program
inner_puzzle: Program
"""NFT state layer inner puzzle"""
p2_puzzle: Program
"""p2 puzzle of the owner, either for ownership layer or standard"""
# ownership layer fields
owner_did: Optional[bytes32]
"""Owner's DID"""
supports_did: bool
"""If the inner puzzle support the DID"""
nft_inner_puzzle_hash: Optional[bytes32]
"""Puzzle hash of the ownership layer inner puzzle """
transfer_program: Optional[Program]
"""Puzzle hash of the transfer program"""
transfer_program_curry_params: Optional[Program]
"""
Curried parameters of the transfer program
[royalty_address, trade_price_percentage, settlement_mod_hash, cat_mod_hash]
"""
royalty_address: Optional[bytes32]
trade_price_percentage: Optional[uint16]
@classmethod
def uncurry(cls: Type[_T_UncurriedNFT], puzzle: Program) -> UncurriedNFT:
"""
@@ -89,14 +108,56 @@ class UncurriedNFT:
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()
nft_mod_hash, metadata, metadata_updater_hash, inner_puzzle = curried_args.as_iter()
data_uris = Program.to([])
data_hash = Program.to(0)
meta_uris = Program.to([])
meta_hash = Program.to(0)
license_uris = Program.to([])
license_hash = Program.to(0)
series_number = Program.to(1)
series_total = Program.to(1)
# 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()
if kv_pair.first().as_atom() == b"mu":
meta_uris = kv_pair.rest()
if kv_pair.first().as_atom() == b"mh":
meta_hash = kv_pair.rest()
if kv_pair.first().as_atom() == b"lu":
license_uris = kv_pair.rest()
if kv_pair.first().as_atom() == b"lh":
license_hash = kv_pair.rest()
if kv_pair.first().as_atom() == b"sn":
series_number = kv_pair.rest()
if kv_pair.first().as_atom() == b"st":
series_total = kv_pair.rest()
current_did = None
transfer_program = None
transfer_program_args = None
royalty_address = None
royalty_percentage = None
nft_inner_puzzle_mod = None
mod, ol_args = inner_puzzle.uncurry()
supports_did = False
if mod == NFT_OWNERSHIP_LAYER:
supports_did = True
log.debug("Parsing ownership layer")
_, current_did, transfer_program, p2_puzzle = ol_args.as_iter()
transfer_program_mod, transfer_program_args = transfer_program.uncurry()
_, royalty_address_p, royalty_percentage = transfer_program_args.as_iter()
royalty_percentage = uint16(royalty_percentage.as_int())
royalty_address = royalty_address_p.atom
current_did = current_did.atom
if current_did == b"":
# For unassigned NFT, set owner DID to None
current_did = None
else:
log.debug("Creating a standard NFT puzzle")
p2_puzzle = inner_puzzle
except Exception as e:
raise ValueError(f"Cannot uncurry NFT state layer: Args {curried_args}") from e
return cls(
@@ -104,19 +165,32 @@ class UncurriedNFT:
nft_state_layer=nft_state_layer,
singleton_struct=singleton_struct,
singleton_mod_hash=singleton_mod_hash,
singleton_launcher_id=singleton_launcher_id,
singleton_launcher_id=singleton_launcher_id.atom,
launcher_puzhash=launcher_puzhash,
metadata=metadata,
data_uris=data_uris,
data_hash=data_hash,
metdata_updater_hash=metdata_updater_hash,
p2_puzzle=p2_puzzle,
metadata_updater_hash=metadata_updater_hash,
meta_uris=meta_uris,
meta_hash=meta_hash,
license_uris=license_uris,
license_hash=license_hash,
series_number=series_number,
series_total=series_total,
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([]),
owner_did=current_did,
supports_did=supports_did,
transfer_program=transfer_program,
transfer_program_curry_params=transfer_program_args,
royalty_address=royalty_address,
trade_price_percentage=royalty_percentage,
nft_inner_puzzle_hash=nft_inner_puzzle_mod,
)
def get_innermost_solution(self, solution: Program) -> Program:
state_layer_inner_solution: Program = solution.at("rrff")
if self.supports_did:
return state_layer_inner_solution.first() # type: ignore
else:
return state_layer_inner_solution
+23 -1
View File
@@ -4,6 +4,10 @@ from typing import Any, Dict, Optional
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.wallet.cat_wallet.cat_outer_puzzle import CATOuterPuzzle
from chia.wallet.nft_wallet.metadata_outer_puzzle import MetadataOuterPuzzle
from chia.wallet.nft_wallet.ownership_outer_puzzle import OwnershipOuterPuzzle
from chia.wallet.nft_wallet.singleton_outer_puzzle import SingletonOuterPuzzle
from chia.wallet.nft_wallet.transfer_program_puzzle import TransferProgramPuzzle
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
"""
@@ -12,6 +16,8 @@ This file provides a central location for acquiring drivers for outer puzzles li
A driver for a puzzle must include the following functions:
- match(self, puzzle: Program) -> Optional[PuzzleInfo]
- Given a puzzle reveal, return a PuzzleInfo object that can be used to reconstruct it later
- get_inner_puzzle(self, constructor: PuzzleInfo, puzzle_reveal: Program) -> Optional[Program]:
- Given a PuzzleInfo object and a puzzle reveal, pull out this outer puzzle's inner puzzle
- asset_id(self, constructor: PuzzleInfo) -> Optional[bytes32]
- Given a PuzzleInfo object, generate a 32 byte ID for use in dictionaries, etc.
- construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program
@@ -26,6 +32,10 @@ A driver for a puzzle must include the following functions:
class AssetType(Enum):
CAT = "CAT"
SINGLETON = "singleton"
METADATA = "metadata"
OWNERSHIP = "ownership"
ROYALTY_TRANSFER_PROGRAM = "royalty transfer program"
def match_puzzle(puzzle: Program) -> Optional[PuzzleInfo]:
@@ -46,12 +56,24 @@ def solve_puzzle(constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program,
)
def get_inner_puzzle(constructor: PuzzleInfo, puzzle_reveal: Program) -> Optional[Program]:
return driver_lookup[AssetType(constructor.type())].get_inner_puzzle(constructor, puzzle_reveal) # type: ignore
def get_inner_solution(constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
return driver_lookup[AssetType(constructor.type())].get_inner_solution(constructor, solution) # type: ignore
def create_asset_id(constructor: PuzzleInfo) -> bytes32:
return driver_lookup[AssetType(constructor.type())].asset_id(constructor) # type: ignore
function_args = [match_puzzle, construct_puzzle, solve_puzzle, create_asset_id]
function_args = [match_puzzle, create_asset_id, construct_puzzle, solve_puzzle, get_inner_puzzle, get_inner_solution]
driver_lookup: Dict[AssetType, Any] = {
AssetType.CAT: CATOuterPuzzle(*function_args),
AssetType.SINGLETON: SingletonOuterPuzzle(*function_args),
AssetType.METADATA: MetadataOuterPuzzle(*function_args),
AssetType.OWNERSHIP: OwnershipOuterPuzzle(*function_args),
AssetType.ROYALTY_TRANSFER_PROGRAM: TransferProgramPuzzle(*function_args),
}
+23 -1
View File
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Any, Dict, Optional
from typing import Any, Dict, List, Optional
from clvm.casts import int_from_bytes
from clvm.SExp import SExp
@@ -42,6 +42,12 @@ class PuzzleInfo:
return False
return True
def __contains__(self, item: str) -> bool:
if item in self.info:
return True
else:
return False
def type(self) -> str:
return str(self.info["type"])
@@ -51,6 +57,22 @@ class PuzzleInfo:
else:
return None
def check_type(self, types: List[str]) -> bool:
if types == []:
if self.also() is None:
return True
else:
return False
else:
if self.type() == types[0]:
types.pop(0)
if self.also():
return self.also().check_type(types) # type: ignore
else:
return self.check_type(types)
else:
return False
@dataclass(frozen=True)
class Solver:
+1
View File
@@ -0,0 +1 @@
ff02ffff01ff04ffff04ff38ffff04ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff2effff04ff02ffff04ff17ffff04ffff02ff26ffff04ff02ffff04ff2fff80808080ff8080808080ff8080808080ffff04ff27ff80808080ffff02ff32ffff04ff02ffff04ff0bffff04ff2fffff01ff01808080808080ffff04ffff01ffffff32ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff0bffff01ff04ffff04ff38ffff04ffff02ff2affff04ff02ffff04ff05ffff04ffff04ffff04ff17ff1380ff8080ff8080808080ffff01ff80808080ffff04ffff04ff10ffff04ff23ffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff808080ff80808080ff80808080ffff02ff32ffff04ff02ffff04ff05ffff04ff1bffff04ffff10ff17ffff010180ff8080808080808080ff8080ff0180ffff02ff36ffff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ff8080808080ff02ff36ffff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff02ff3effff04ff02ffff04ff05ff80808080ff808080808080ffffff02ffff03ffff07ff0580ffff01ff04ff29ffff02ff26ffff04ff02ffff04ff0dff8080808080ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0b80ffff01ff10ff13ffff02ff2effff04ff02ffff04ff05ffff04ff1bff808080808080ffff011580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
@@ -69,4 +69,24 @@
(tree-hash-of-apply function-hash
(build-curry-list reversed-curry-parameter-hashes (sha256 ONE ONE)))
)
(defconstant b32 32)
(defun-inline size_b32 (var)
(= (strlen var) b32)
)
(defun calculate_coin_id (parent puzzlehash amount)
(if (all (size_b32 parent) (size_b32 puzzlehash) (> amount -1))
(sha256 parent puzzlehash amount)
(x)
)
)
; 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 1 TREE)))
)
-8
View File
@@ -17,14 +17,6 @@
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(defun sha256tree
(TREE)
(if (l TREE)
(sha256 2 (sha256tree (f TREE)) (sha256tree (r TREE)))
(sha256 1 TREE)
)
)
(defun-inline tree_branch_hash (left right) (sha256 2 left right))
(defun calculate_merkle_root (path current_hash additional_steps)
+1
View File
@@ -0,0 +1 @@
ff02ffff01ff04ffff04ffff013effff04ffff02ff02ffff04ff02ffff04ff05ff80808080ff808080ff8080ffff04ffff01ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff02ffff04ff02ffff04ff09ff80808080ffff02ff02ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
-85
View File
@@ -1,85 +0,0 @@
(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)
)
)
)
)
@@ -1 +0,0 @@
ff02ffff01ff04ffff04ff10ffff04ff12ff808080ffff02ffff03ff8202ffffff01ff02ffff03ffff09ffff02ff3effff04ff02ffff04ff820bffff80808080ff2f80ffff01ff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff02ff3effff04ff02ffff04ffff04ff8217ffffff04ff8202ffff808080ff8080808080ff808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff2fffff04ff5fffff04ff81bfffff04ff8202ffffff04ffff02ff820bffffff04ff81bfffff04ff5fffff04ff0bffff04ff17ffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff8217ffff80808080808080808080ffff01ff808080808080808080808080ffff01ff088080ff0180ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff81bfff80808080ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff0bff12ff2f80ffff04ffff0bff12ff1780ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff17ff8080ff8080808080ffff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff016180ff808080ffff04ffff04ff2cffff04ff17ff808080ff8080808080ff018080ffff04ffff01ffffff49ff3f02ff33ff3e04ffff01ff0102ffffff02ffff03ff05ffff01ff02ff26ffff04ff02ffff04ff0dffff04ffff0bff3affff0bff12ff3c80ffff0bff3affff0bff3affff0bff12ff2a80ff0980ffff0bff3aff0bffff0bff12ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff82017fffff01ff02ffff03ffff09ff82047fffff0181ea80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff820a7fff80808080ffff04ffff02ff3effff04ff02ffff04ff82167fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff01ff018080808080808080808080ffff01ff04ff82027fffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff04ff8202ffff80808080808080808080808080ff0180ffff01ff02ffff03ff8202ffff80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff02ff3effff04ff02ffff04ff2fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ff808080ff018080ff0180ffff0bff3affff0bff12ff3880ffff0bff3affff0bff3affff0bff12ff2a80ff0580ffff0bff3affff02ff26ffff04ff02ffff04ff07ffff04ffff0bff12ff1280ff8080808080ffff0bff12ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bff12ff058080ff0180ff018080
@@ -1 +0,0 @@
ae8ee2bf9e0bcc189d4d8efa3c8281235981abd75ade592ba71930aa6881f820
@@ -1,20 +0,0 @@
(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)
)
)
@@ -1 +0,0 @@
ff02ffff01ff02ffff03ff0bffff01ff04ffff02ff02ffff04ff02ffff04ff05ffff04ff0bff8080808080ffff01ff808080ffff01ff04ff05ffff01ff80808080ff0180ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080
@@ -1,21 +1,30 @@
(mod (CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH solution)
(mod (CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH (key . new_url))
; 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)
; Add uri to a field
(defun add_url (METADATA key 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))
(if (= (f (f METADATA)) key)
(c (c key (c new_url (r (f METADATA)))) (r METADATA))
(c (f METADATA) (add_url (r METADATA) key 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)
(list
(list
(if (all key new_url)
(if (any (= key "mu") (= key "lu") (= key "u"))
(add_url CURRENT_METADATA key new_url)
CURRENT_METADATA
)
CURRENT_METADATA
)
METADATA_UPDATER_PUZZLE_HASH)
0
)
)
@@ -1 +1 @@
ff02ffff01ff04ffff04ffff02ffff03ff17ffff01ff02ff02ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff010580ff0180ffff04ff0bff808080ffff01ff808080ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080
ff02ffff01ff04ffff04ffff02ffff03ffff22ff27ff3780ffff01ff02ffff03ffff21ffff09ff27ffff01826d7580ffff09ff27ffff01826c7580ffff09ff27ffff01758080ffff01ff02ff02ffff04ff02ffff04ff05ffff04ff27ffff04ff37ff808080808080ffff010580ff0180ffff010580ff0180ffff04ff0bff808080ffff01ff808080ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ff0b80ffff01ff04ffff04ff0bffff04ff17ff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bffff04ff17ff8080808080808080ff0180ff8080ff0180ff018080
@@ -1 +1 @@
81970d352e6a39a241eaf8ca510a0e669e40d778ba612621c60a50ef6cf29c7b
fe8a4b4e27a2e29a4d3fc7ce9d527adbcaccbab6ada3903ccf3ba9a769d2d78b
@@ -0,0 +1,28 @@
(mod (CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH solution)
; solution is (new_url new_metadata_updater_puzhash)
; 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)
; NOTE THIS PROGRAM IS FOR TESTING ONLY - USE IN DEPLOYMENT AT YOUR OWN PERIL
; 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-inline assert_bytes32 (value)
(= (strlen value) 32)
)
; main
; returns ((new_metadata new_metadata_updater_puzhash) conditions)
(list (list (if (f solution) (add_url CURRENT_METADATA (f solution)) CURRENT_METADATA) (if (assert_bytes32 (f (r solution))) (f (r solution)) METADATA_UPDATER_PUZZLE_HASH)) 0)
)
@@ -0,0 +1 @@
ff02ffff01ff04ffff04ffff02ffff03ff27ffff01ff02ff02ffff04ff02ffff04ff05ffff04ff27ff8080808080ffff010580ff0180ffff04ffff02ffff03ffff09ffff0dff5780ffff012080ffff0157ffff010b80ff0180ff808080ffff01ff808080ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080
@@ -0,0 +1 @@
0b1ffba1601777c06b78ab38636e9624f2f8da73be9b36e0ce17c8d8ef3bad9f
@@ -0,0 +1,100 @@
(mod (
NFT_OWNERSHIP_LAYER_MOD_HASH
CURRENT_OWNER
TRANSFER_PROGRAM
INNER_PUZZLE
inner_solution
)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(include utility_macros.clib)
(defconstant NEW_OWNER_CONDITION -10)
(defconstant ANNOUNCEMENT_PREFIX 0xad4c) ; first 2 bytes of (sha256 "Ownership Layer")
(defun-inline nft_ownership_layer_puzzle_hash (NFT_OWNERSHIP_LAYER_MOD_HASH new_owner TRANSFER_PROGRAM inner_puzzle_hash)
(puzzle-hash-of-curried-function NFT_OWNERSHIP_LAYER_MOD_HASH
inner_puzzle_hash
(sha256tree TRANSFER_PROGRAM)
(sha256 ONE new_owner)
(sha256 ONE NFT_OWNERSHIP_LAYER_MOD_HASH)
)
)
(defun construct_end_conditions (NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM odd_args (new_owner new_tp conditions))
(c
(c
CREATE_COIN
(c
(nft_ownership_layer_puzzle_hash NFT_OWNERSHIP_LAYER_MOD_HASH new_owner (if new_tp new_tp TRANSFER_PROGRAM) (f odd_args))
(r odd_args)
)
)
conditions
)
)
(defun wrap_odd_create_coins (NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions conditions odd_args tp_output)
(if conditions
(if (= (f (f conditions)) CREATE_COIN)
(if (= (logand (f (r (r (f conditions))))) ONE)
(assert (not odd_args)
; then
(wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions (r conditions) (r (f conditions)) tp_output)
)
(c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions (r conditions) odd_args tp_output))
)
(if (= (f (f conditions)) NEW_OWNER_CONDITION)
(assert (not tp_output)
(c
(list CREATE_PUZZLE_ANNOUNCEMENT (concat ANNOUNCEMENT_PREFIX (sha256tree (r (f conditions)))))
(wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions (r conditions) odd_args (a TRANSFER_PROGRAM (list CURRENT_OWNER all_conditions (r (f conditions)))))
)
)
(if (= (f (f conditions)) CREATE_PUZZLE_ANNOUNCEMENT)
(assert (not (and
(= 34 (strlen (f (r (f conditions)))))
(= (substr (f (r (f conditions))) 0 2) ANNOUNCEMENT_PREFIX) ; lazy eval
))
; then
(c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions (r conditions) odd_args tp_output))
)
(c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions (r conditions) odd_args tp_output))
)
)
)
; odd_args is guaranteed to not be nil or else we'll have a path into atom error
(construct_end_conditions NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM odd_args
(if tp_output
tp_output
(a TRANSFER_PROGRAM (list CURRENT_OWNER all_conditions ()))
)
)
)
)
(defun main (
NFT_OWNERSHIP_LAYER_MOD_HASH
TRANSFER_PROGRAM
CURRENT_OWNER
conditions
)
(wrap_odd_create_coins
NFT_OWNERSHIP_LAYER_MOD_HASH
TRANSFER_PROGRAM
CURRENT_OWNER
conditions
conditions
() ()
)
)
; main
(main
NFT_OWNERSHIP_LAYER_MOD_HASH
TRANSFER_PROGRAM
CURRENT_OWNER
(a INNER_PUZZLE inner_solution)
)
)
@@ -0,0 +1 @@
ff02ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff82ad4cff0233ffff3e04ff81f601ffffff0102ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff32ffff0bff3cff3480ffff0bff32ffff0bff32ffff0bff3cff2280ff0980ffff0bff32ff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff27ffff04ffff02ff2effff04ff02ffff04ffff02ffff03ff81afffff0181afffff010b80ff0180ff80808080ffff04ffff0bff3cff4f80ffff04ffff0bff3cff0580ff8080808080808080ff378080ff82016f80ffffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff2fffff01ff80ff808080808080808080ff0bff32ffff0bff3cff2880ffff0bff32ffff0bff32ffff0bff3cff2280ff0580ffff0bff32ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff5fffff01ff02ffff03ffff09ff82011fff3880ffff01ff02ffff03ffff09ffff18ff82059f80ff3c80ffff01ff02ffff03ffff20ff81bf80ffff01ff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff82019fffff04ff82017fff80808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fff808080808080808080808080ff0180ffff01ff02ffff03ffff09ff82011fff2c80ffff01ff02ffff03ffff20ff82017f80ffff01ff04ffff04ff24ffff04ffff0eff10ffff02ff2effff04ff02ffff04ff82019fff8080808080ff808080ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ffff02ff0bffff04ff17ffff04ff2fffff04ff82019fff8080808080ff8080808080808080808080ffff01ff088080ff0180ffff01ff02ffff03ffff09ff82011fff2480ffff01ff02ffff03ffff20ffff02ffff03ffff09ffff0122ffff0dff82029f8080ffff01ff02ffff03ffff09ffff0cff82029fff80ffff010280ff1080ffff01ff0101ff8080ff0180ff8080ff018080ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fff8080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fff808080808080808080808080ff018080ff018080ff0180ffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff81bfffff04ffff02ffff03ff82017fffff0182017fffff01ff02ff0bffff04ff17ffff04ff2fffff01ff808080808080ff0180ff8080808080808080ff0180ff018080
@@ -0,0 +1 @@
c5abea79afaa001b5427dfa0c8cf42ca6f38f5841b78f9b3c252733eb2de2726
@@ -1,83 +0,0 @@
(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)))
)
)
)
)
)
@@ -1 +0,0 @@
ff02ffff01ff04ffff04ffff04ff38ffff04ff81bfff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff5fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff17ff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e808080ff81bf80ff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff81bfff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e80808080ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff8202ffffff04ff15ff808080808080808080ff8080ffff04ffff01ffffff3fff0281eaffff3304ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff3480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff5dffff04ff2dffff04ffff0bffff0101ff5380ffff04ffff0bffff0101ff5d80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff24ffff04ff09ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
@@ -1 +0,0 @@
03557552e0bf8b200d8be8089f54786195ab8c07e3e07c39641f2f4f1fc2e48e
@@ -0,0 +1,78 @@
(mod
(
SINGLETON_STRUCT
ROYALTY_ADDRESS
TRADE_PRICE_PERCENTAGE
Current_Owner ; Truth
conditions ; Truth
solution ; created from the NFT's inner puzzle - solution is (new_owner trade_prices_list new_did_inner_hash)
)
; This is a transfer program - which must return (new_owner, Optional[new_transfer_program], conditions)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(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 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 trade_prices_list my_nft_id)
(if trade_prices_list
(c
(list
ASSERT_PUZZLE_ANNOUNCEMENT
(sha256
(f (r (f trade_prices_list)))
(sha256tree (c my_nft_id (list (list ROYALTY_ADDRESS (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE) (list ROYALTY_ADDRESS)))))
)
)
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE (r trade_prices_list) my_nft_id)
)
()
)
)
; main
; Returning (new_owner new_transfer_program conditions)
; solution is (new_owner trade_prices_list new_did_inner_hash)
(if solution
(list
(f solution)
0
(if (all (f solution) (not (= (f solution) Current_Owner)))
(c
(list
ASSERT_PUZZLE_ANNOUNCEMENT
(sha256
(calculate_full_puzzle_hash (get_singleton_struct SINGLETON_STRUCT (f solution)) (f (r (r solution))))
(f (r SINGLETON_STRUCT))
)
)
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE (f (r solution)) (f (r SINGLETON_STRUCT)))
)
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE (f (r solution)) (f (r SINGLETON_STRUCT)))
)
)
(list Current_Owner () ())
)
)
@@ -0,0 +1 @@
ff02ffff01ff02ffff03ff81bfffff01ff04ff82013fffff04ff80ffff04ffff02ffff03ffff22ff82013fffff20ffff09ff82013fff2f808080ffff01ff04ffff04ff10ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff8205bfffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff82013fff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff16ffff04ff02ffff04ff0bffff04ff17ffff04ff8202bfffff04ff15ff8080808080808080ffff01ff02ff16ffff04ff02ffff04ff0bffff04ff17ffff04ff8202bfffff04ff15ff8080808080808080ff0180ff80808080ffff01ff04ff2fffff01ff80ff80808080ff0180ffff04ffff01ffffff3f02ff04ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff1480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff04ffff04ff10ffff04ffff0bff81a7ffff02ff3effff04ff02ffff04ffff04ff2fffff04ffff04ff05ffff04ffff05ffff14ffff12ff47ff0b80ff128080ffff04ffff04ff05ff8080ff80808080ff808080ff8080808080ff808080ffff02ff16ffff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fff8080808080808080ff8080ff0180ffff0bff2affff0bff2cff1880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
@@ -0,0 +1 @@
025dee0fb1e9fa110302a7e9bfb6e381ca09618e2778b0184fa5c6b275cfce1f
+44 -69
View File
@@ -3,97 +3,72 @@
METADATA
METADATA_UPDATER_PUZZLE_HASH
INNER_PUZZLE
solution ; either to inner puzzle or metadata updater
my_amount
inner_solution
)
(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)
)
)
(include utility_macros.clib)
(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)
(sha256tree 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)
; this function does two things - it wraps the odd value create coins, and it also filters out all negative conditions
; odd_coin_params is (puzhash amount ...)
; new_metadata_info is ((METADATA METADATA_UPDATER_PUZZLE_HASH) conditions)
(defun wrap_odd_create_coins (NFT_STATE_LAYER_MOD_HASH conditions odd_coin_params new_metadata_info metadata_seen)
(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)))
)
(if (logand (f (r (r (f conditions)))) ONE)
(assert (not odd_coin_params)
(wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (r conditions) (r (f conditions)) new_metadata_info metadata_seen)
)
(c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (r conditions) odd_coin_params new_metadata_info metadata_seen))
)
(if (= (f (f conditions)) -24)
(wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (r conditions) odd_coin_params
(assert (all
(= (sha256tree (f (r (f conditions)))) (f (r (f new_metadata_info))))
(not metadata_seen)
)
; then
(a (f (r (f conditions))) (list (f (f new_metadata_info)) (f (r (f new_metadata_info))) (f (r (r (f conditions))))))
)
ONE ; the metadata update has been seen now
)
(c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (r conditions) odd_coin_params new_metadata_info metadata_seen))
)
))
(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)
(c
(c CREATE_COIN
(c
(nft_state_layer_puzzle_hash
NFT_STATE_LAYER_MOD_HASH
(f (f new_metadata_info))
(f (r (f new_metadata_info)))
(f odd_coin_params) ; metadata updater solution
)
(r odd_coin_params)
)
)
(check_for_metadata_prog_reveal METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA (r conditions_loop))
(f (r new_metadata_info)) ; metadata_updater conditions
)
(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
)
(wrap_odd_create_coins
NFT_STATE_LAYER_MOD_HASH
(a INNER_PUZZLE inner_solution)
()
(list (list METADATA METADATA_UPDATER_PUZZLE_HASH) 0) ; if the magic condition is never seen, this is the information we us to recurry
()
)
)
+1 -1
View File
@@ -1 +1 @@
ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfffff01ff8080808080808080ffff04ffff01ffffff49ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ffff0181e880ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff05ffff04ff820167ff8080808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ffff04ff0bffff04ff05ff808080ffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff2bffff01ff02ffff03ffff09ff818bff3880ffff01ff02ffff03ffff18ff8202cbff3480ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff4bff80808080808080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff808080808080808080ff0180ffff01ff02ffff03ffff15ff818bff8080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff8080808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff8080808080808080ff018080ff0180ffff01ff02ffff03ff2fffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff81afffff04ffff0bff34ff5380ffff04ffff02ff2effff04ff02ffff04ff23ff80808080ffff04ffff0bff34ff0580ff8080808080808080ffff04ff17ff8201ef808080ff8080ffff01ff088080ff018080ff0180ff018080
ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080
@@ -1 +1 @@
dd8135d546e291df295b376aa89fc409c8c50d7f655d1ff4e845637901bc2f8f
a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2
@@ -1,139 +0,0 @@
(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)
)
)
)
@@ -1 +0,0 @@
ff02ffff01ff02ff2affff04ff02ffff04ff05ffff04ff0bffff04ff820affffff04ffff02ff3affff04ff02ffff04ff17ffff04ff2fffff04ff0bffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffff80808080808080808080ff80808080808080ffff04ffff01ffffffff3f02ff333effff0401ff01ff82271002ffffffff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff22ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff7cffff0bff34ff2480ffff0bff7cffff0bff7cffff0bff34ff2c80ff0980ffff0bff7cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff04ffff04ffff0181eaffff04ffff02ff22ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff04ff0bff80808080ff2f80ffff012f80ff0180ff02ffff03ff82017fffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ffff02ff7effff04ff02ffff04ff82017fff80808080ff8080808080808080808080ffff01ff04ffff04ff38ffff01ff808080ff808080ff0180ffffff04ffff04ff38ffff04ff8202ffff808080ffff04ffff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff81bfffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff5fff1d8080ff80808080ff808080808080ff8202ffff1580ff808080ffff02ff36ffff04ff02ffff04ff17ffff04ff82017fffff04ff15ff8080808080808080ff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff5dffff04ff2dffff04ffff0bff34ff5380ffff04ffff0bff34ff5d80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff28ffff04ff09ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ffff0bff7cffff0bff34ff3080ffff0bff7cffff0bff7cffff0bff34ff2c80ff0580ffff0bff7cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080
@@ -1 +0,0 @@
519b806c31cce5280fa37695149521a301f13f5696d1fd1eeee1856c2d14965c
-8
View File
@@ -9,14 +9,6 @@
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
; 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 1 TREE)
)
)
;; 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_MOD_HASH LAUNCHER_ID LAUNCHER_PUZZLE_HASH inner_puzzle_hash)
@@ -16,14 +16,6 @@
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
; 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 1 TREE)
)
)
;; 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 delayed_spend (SECONDS_DELAY DELAYED_PUZZLE_HASH amount)
+13 -3
View File
@@ -1,4 +1,4 @@
from typing import List, Tuple, Optional
from typing import Iterator, List, Tuple, Optional
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
@@ -157,6 +157,14 @@ MELT_CONDITION = [ConditionOpcode.CREATE_COIN, 0, ESCAPE_VALUE]
#
def match_singleton_puzzle(puzzle: Program) -> Tuple[bool, Iterator[Program]]:
mod, curried_args = puzzle.uncurry()
if mod == SINGLETON_MOD:
return True, curried_args.as_iter()
else:
return False, iter(())
# Given the parent and amount of the launcher coin, return the launcher coin
def generate_launcher_coin(coin: Coin, amount: uint64) -> Coin:
return Coin(coin.name(), SINGLETON_LAUNCHER_HASH, amount)
@@ -249,9 +257,11 @@ def lineage_proof_for_coinsol(coin_spend: CoinSpend) -> LineageProof:
# Return the puzzle reveal of a singleton with specific ID and innerpuz
def puzzle_for_singleton(launcher_id: bytes32, inner_puz: Program) -> Program:
def puzzle_for_singleton(
launcher_id: bytes32, inner_puz: Program, launcher_hash: bytes32 = SINGLETON_LAUNCHER_HASH
) -> Program:
return SINGLETON_MOD.curry(
(SINGLETON_MOD_HASH, (launcher_id, SINGLETON_LAUNCHER_HASH)),
(SINGLETON_MOD_HASH, (launcher_id, launcher_hash)),
inner_puz,
)
@@ -13,28 +13,7 @@
(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)))
(include utility_macros.clib)
(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)))
@@ -53,6 +32,10 @@
(c (f condition) (c (calculate_full_puzzle_hash SINGLETON_STRUCT (f (r condition))) (r (r condition))))
)
(defun is_odd_create_coin (condition)
(and (= (f condition) CREATE_COIN) (logand (f (r (r condition))) 1))
)
; 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
@@ -65,7 +48,7 @@
(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))
(if (is_odd_create_coin (f conditions))
; check that we haven't already found one
(assert (not has_odd_output_been_found)
; then
@@ -103,7 +86,7 @@
(verify_lineage_proof
SINGLETON_STRUCT
; calculate our parent's ID
(sha256
(calculate_coin_id
(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
@@ -1 +1 @@
ff02ffff01ff02ffff03ffff18ff2fff3c80ffff01ff04ffff04ff10ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff0bff27ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff02ffff03ff77ffff0181b7ffff015780ff018080ffff04ff77ff808080808080ffff02ff26ffff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffff49ff4702ff33ff0401ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff02ffff03ffff02ffff03ffff09ff23ff1480ffff01ff02ffff03ffff18ff81b3ff3c80ffff01ff0101ff8080ff0180ff8080ff0180ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff3cff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff3cff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ff0bff2affff0bff3cff3880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff3cff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff28ffff04ff0bff808080ffff01ff088080ff0180ff018080
ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080
@@ -1 +1 @@
f1e8350cec62f8204aaf867cc3c12cae369f619258206616108c6cfd7be760b3
7faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9f
@@ -0,0 +1,347 @@
from typing import Iterator, List, Optional, Tuple
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.condition_opcodes import ConditionOpcode
from chia.util.hash import std_hash
from chia.util.ints import uint64
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.puzzles.load_clvm import load_clvm
SINGLETON_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
SINGLETON_MOD_HASH = SINGLETON_MOD.get_tree_hash()
P2_SINGLETON_MOD = load_clvm("p2_singleton.clvm")
P2_SINGLETON_OR_DELAYED_MOD = load_clvm("p2_singleton_or_delayed_puzhash.clvm")
SINGLETON_LAUNCHER = load_clvm("singleton_launcher.clvm")
SINGLETON_LAUNCHER_HASH = SINGLETON_LAUNCHER.get_tree_hash()
ESCAPE_VALUE = -113
MELT_CONDITION = [ConditionOpcode.CREATE_COIN, 0, ESCAPE_VALUE]
#
# An explanation of how this functions from a user's perspective
#
# Consider that you have some coin A that you want to create a singleton
# containing some inner puzzle I from with amount T. We'll call the Launcher
# coin, which is created from A "Launcher" and the first iteration of the
# singleton, called the "Eve" spend, Eve. When spent, I yields a coin
# running I' and so on in a singleton specific way described below.
#
# The structure of this on the blockchain when done looks like this
#
# ,------------.
# | Coin A |
# `------------'
# |
# ------------------ Atomic Transaction 1 -----------------
# v
# .------------. .-------------------------------.
# | Launcher |------>| Eve Coin Containing Program I |
# `------------' `-------------------------------'
# |
# -------------------- End Transaction 1 ------------------\
# | > The Eve coin
# --------------- (2) Transaction With I ------------------/ may also be
# | spent
# v simultaneously
# .-----------------------------------.
# | Running Singleton With Program I' |
# `-----------------------------------'
# |
# --------------------- End Transaction 2 ------------------
# |
# --------------- (3) Transaction With I' ------------------
# ...
#
#
# == Practial use of singleton_top_layer.py ==
#
# 1) Designate some coin as coin A
#
# 2) call puzzle_for_singleton with that coin's name (it is the Parent of the
# Launch coin), and the initial inner puzzle I, curried as appropriate for
# its own purpose. Adaptations of the program I and its descendants are
# required as below.
#
# 3) call launch_conditions_and_coinsol to get a set of "launch_conditions",
# which will be used to spend standard coin A, and a "spend", which spends
# the Launcher created by the application of "launch_conditions" to A in a
# spend. These actions must be done in the same spend bundle.
#
# One can create a SpendBundle containing the spend of A giving it the
# argument list (() (q . launch_conditions) ()) and then append "spend" onto
# its .coin_spends to create a combined spend bundle.
#
# 4) submit the combine spend bundle.
#
# 5) Remember the identity of the Launcher coin:
#
# Coin(A.name(), SINGLETON_LAUNCHER_HASH, amount)
#
# A singleton has been created like this:
#
# Coin(Launcher.name(), puzzle_for_singleton(Launcher.name(), I), amount)
#
#
# == To spend the singleton requires some host side setup ==
#
# The singleton adds an ASSERT_MY_COIN_ID to constrain it to the coin that
# matches its own conception of itself. It consumes a "LineageProof" object
# when spent that must be constructed so. We'll call the singleton we intend
# to spend "S".
#
# Specifically, the required puzzle is the Inner puzzle I for the parent of S
# unless S is the Eve coin, in which case it is None.
# So to spend S', the second singleton, I is used, and to spend S'', I' is used.
# We'll call this puzzle hash (or None) PH.
#
# If this is the Eve singleton:
#
# PH = None
# L = LineageProof(Launcher, PH, amount)
#
# - Note: the Eve singleton's .parent_coin_info should match Launcher here.
#
# Otherwise
#
# PH = ParentOf(S).inner_puzzle_hash
# L = LineageProof(ParentOf(S).name(), PH, amount)
#
# - Note: ParentOf(S).name is the .parent_coin_info member of the
# coin record for S.
#
# Now the coin S can be spent.
# The puzzle to use in the spend is given by
#
# puzzle_for_singleton(S.name(), I'.puzzle_hash())
#
# and the arguments are given by (with the argument list to I designated AI)
#
# solution_for_singleton(L, amount, AI)
#
# Note that AI contains dynamic arguments to puzzle I _after_ the singleton
# truths.
#
#
# Adapting puzzles to the singleton
#
# 1) For the puzzle to create a coin from inside the singleton it will need the
# following values to be added to its curried in arguments:
#
# - A way to compute its own puzzle has for each of I' and so on. This can
# be accomplished by giving it its uncurried puzzle hash and using
# puzzle-hash-of-curried-function to compute it. Although full_puzzle_hash
# is used for some arguments, the inputs to all singleton_top_layer
# functions is the inner puzzle.
#
# - the name() of the Launcher coin (which you can compute from a Coin
# object) if you're not already using it in I puzzle for some other
# reason.
#
# 2) A non-curried argument called "singleton_truths" will be passed to your
# program. It is not required to use anything inside.
#
# There is little value in not receiving this argument via the adaptations
# below as a standard puzzle can't be used anyway. To work the result must
# be itself a singleton, and the singleton does not change the puzzle hash
# in an outgoing CREATE_COIN to cause it to be one.
#
# With this modification of the program I done, I and descendants will
# continue to produce I', I'' etc.
#
# The actual CREATE_COIN puzzle hash will be the result of
# this. The Launcher ID referred to here is the name() of
# the Launcher coin as above.
#
def match_singleton_puzzle(puzzle: Program) -> Tuple[bool, Iterator[Program]]:
mod, curried_args = puzzle.uncurry()
if mod == SINGLETON_MOD:
return True, curried_args.as_iter()
else:
return False, iter(())
# Given the parent and amount of the launcher coin, return the launcher coin
def generate_launcher_coin(coin: Coin, amount: uint64) -> Coin:
return Coin(coin.name(), SINGLETON_LAUNCHER_HASH, amount)
def remove_singleton_truth_wrapper(puzzle: Program) -> Program:
inner_puzzle: Program = puzzle.rest().first().rest()
return inner_puzzle
# Take standard coin and amount -> launch conditions & launcher coin solution
def launch_conditions_and_coinsol(
coin: Coin,
inner_puzzle: Program,
comment: List[Tuple[str, str]],
amount: uint64,
) -> Tuple[List[Program], CoinSpend]:
if (amount % 2) == 0:
raise ValueError("Coin amount cannot be even. Subtract one mojo.")
launcher_coin: Coin = generate_launcher_coin(coin, amount)
curried_singleton: Program = SINGLETON_MOD.curry(
(SINGLETON_MOD_HASH, (launcher_coin.name(), SINGLETON_LAUNCHER_HASH)),
inner_puzzle,
)
launcher_solution = Program.to(
[
curried_singleton.get_tree_hash(),
amount,
comment,
]
)
create_launcher = Program.to(
[
ConditionOpcode.CREATE_COIN,
SINGLETON_LAUNCHER_HASH,
amount,
],
)
assert_launcher_announcement = Program.to(
[
ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT,
std_hash(launcher_coin.name() + launcher_solution.get_tree_hash()),
],
)
conditions = [create_launcher, assert_launcher_announcement]
launcher_coin_spend = CoinSpend(
launcher_coin,
SINGLETON_LAUNCHER,
launcher_solution,
)
return conditions, launcher_coin_spend
# Take a coin solution, return a lineage proof for their child to use in spends
def lineage_proof_for_coinsol(coin_spend: CoinSpend) -> LineageProof:
parent_name: bytes32 = coin_spend.coin.parent_coin_info
inner_puzzle_hash: Optional[bytes32] = None
if coin_spend.coin.puzzle_hash != SINGLETON_LAUNCHER_HASH:
full_puzzle = Program.from_bytes(bytes(coin_spend.puzzle_reveal))
r = full_puzzle.uncurry()
if r is not None:
_, args = r
_, inner_puzzle = list(args.as_iter())
inner_puzzle_hash = inner_puzzle.get_tree_hash()
amount: uint64 = coin_spend.coin.amount
return LineageProof(
parent_name,
inner_puzzle_hash,
amount,
)
# Return the puzzle reveal of a singleton with specific ID and innerpuz
def puzzle_for_singleton(
launcher_id: bytes32, inner_puz: Program, launcher_hash: bytes32 = SINGLETON_LAUNCHER_HASH
) -> Program:
return SINGLETON_MOD.curry(
(SINGLETON_MOD_HASH, (launcher_id, launcher_hash)),
inner_puz,
)
# Return a solution to spend a singleton
def solution_for_singleton(
lineage_proof: LineageProof,
amount: uint64,
inner_solution: Program,
) -> Program:
if lineage_proof.inner_puzzle_hash is None:
parent_info = [
lineage_proof.parent_name,
lineage_proof.amount,
]
else:
parent_info = [
lineage_proof.parent_name,
lineage_proof.inner_puzzle_hash,
lineage_proof.amount,
]
solution: Program = Program.to([parent_info, amount, inner_solution])
return solution
# Create a coin that a singleton can claim
def pay_to_singleton_puzzle(launcher_id: bytes32) -> Program:
return P2_SINGLETON_MOD.curry(SINGLETON_MOD_HASH, launcher_id, SINGLETON_LAUNCHER_HASH)
# Create a coin that a singleton can claim or that can be sent to another puzzle after a specified time
def pay_to_singleton_or_delay_puzzle(launcher_id: bytes32, delay_time: uint64, delay_ph: bytes32) -> Program:
return P2_SINGLETON_OR_DELAYED_MOD.curry(
SINGLETON_MOD_HASH,
launcher_id,
SINGLETON_LAUNCHER_HASH,
delay_time,
delay_ph,
)
# Solution for EITHER p2_singleton or the claiming spend case for p2_singleton_or_delayed_puzhash
def solution_for_p2_singleton(p2_singleton_coin: Coin, singleton_inner_puzhash: bytes32) -> Program:
solution: Program = Program.to([singleton_inner_puzhash, p2_singleton_coin.name()])
return solution
# Solution for the delayed spend case for p2_singleton_or_delayed_puzhash
def solution_for_p2_delayed_puzzle(output_amount: uint64) -> Program:
solution: Program = Program.to([output_amount, []])
return solution
# Get announcement conditions for singleton solution and full CoinSpend for the claimed coin
def claim_p2_singleton(
p2_singleton_coin: Coin,
singleton_inner_puzhash: bytes32,
launcher_id: bytes32,
delay_time: Optional[uint64] = None,
delay_ph: Optional[bytes32] = None,
) -> Tuple[Program, Program, CoinSpend]:
assertion = Program.to([ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, std_hash(p2_singleton_coin.name() + b"$")])
announcement = Program.to([ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT, p2_singleton_coin.name()])
if delay_time is None or delay_ph is None:
puzzle: Program = pay_to_singleton_puzzle(launcher_id)
else:
puzzle = pay_to_singleton_or_delay_puzzle(
launcher_id,
delay_time,
delay_ph,
)
claim_coinsol = CoinSpend(
p2_singleton_coin,
puzzle,
solution_for_p2_singleton(p2_singleton_coin, singleton_inner_puzhash),
)
return assertion, announcement, claim_coinsol
# Get the CoinSpend for spending to a delayed puzzle
def spend_to_delayed_puzzle(
p2_singleton_coin: Coin,
output_amount: uint64,
launcher_id: bytes32,
delay_time: uint64,
delay_ph: bytes32,
) -> CoinSpend:
claim_coinsol = CoinSpend(
p2_singleton_coin,
pay_to_singleton_or_delay_puzzle(launcher_id, delay_time, delay_ph),
solution_for_p2_delayed_puzzle(output_amount),
)
return claim_coinsol
+17
View File
@@ -0,0 +1,17 @@
(
(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)
)
)
+116 -30
View File
@@ -12,6 +12,8 @@ from chia.types.spend_bundle import SpendBundle
from chia.util.db_wrapper import DBWrapper
from chia.util.hash import std_hash
from chia.util.ints import uint32, uint64
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.payment import Payment
from chia.wallet.puzzle_drivers import PuzzleInfo
from chia.wallet.trade_record import TradeRecord
@@ -23,6 +25,9 @@ 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_coin_record import WalletCoinRecord
from chia.wallet.puzzles.load_clvm import load_clvm
OFFER_MOD = load_clvm("settlement_payments.clvm")
class TradeManager:
@@ -58,7 +63,7 @@ class TradeManager:
- get_asset_id() -> bytes32
- Finally, you must make sure that your wallet will respond appropriately when these WSM methods are called:
- get_wallet_for_puzzle_info(puzzle_info: PuzzleInfo) -> <Your wallet>
- create_wallet_for_puzzle_info(puzzle_info: PuzzleInfo) -> <Your wallet>
- create_wallet_for_puzzle_info(..., puzzle_info: PuzzleInfo) -> <Your wallet> (See cat_wallet.py for full API)
- get_wallet_for_asset_id(asset_id: bytes32) -> <Your wallet>
"""
@@ -222,15 +227,13 @@ class TradeManager:
if wallet is None:
continue
new_ph = await wallet.get_new_puzzlehash()
# This should probably not switch on whether or not we're spending a CAT but it has to for now
# ATTENTION: new_wallets
if wallet.type() == WalletType.CAT:
txs = await wallet.generate_signed_transaction(
[coin.amount], [new_ph], fee=fee_to_pay, coins={coin}, ignore_max_send_amount=True
)
all_txs.extend(txs)
if wallet.type() == WalletType.NFT:
new_ph = await wallet.wallet_state_manager.main_wallet.get_new_puzzlehash()
else:
new_ph = await wallet.get_new_puzzlehash()
# This should probably not switch on whether or not we're spending a XCH but it has to for now
if wallet.type() == WalletType.STANDARD_WALLET:
if fee_to_pay > coin.amount:
selected_coins: Set[Coin] = await wallet.select_coins(
uint64(fee_to_pay - coin.amount),
@@ -247,6 +250,12 @@ class TradeManager:
ignore_max_send_amount=True,
)
all_txs.append(tx)
else:
# ATTENTION: new_wallets
txs = await wallet.generate_signed_transaction(
[coin.amount], [new_ph], fee=fee_to_pay, coins={coin}, ignore_max_send_amount=True
)
all_txs.extend(txs)
fee_to_pay = uint64(0)
cancellation_addition = Coin(coin.name(), new_ph, coin.amount)
@@ -329,6 +338,7 @@ class TradeManager:
try:
coins_to_offer: Dict[Union[int, bytes32], List[Coin]] = {}
requested_payments: Dict[Optional[bytes32], List[Payment]] = {}
offer_dict_no_ints: Dict[Optional[bytes32], int] = {}
for id, amount in offer_dict.items():
if amount > 0:
if isinstance(id, int):
@@ -372,18 +382,33 @@ class TradeManager:
elif amount == 0:
raise ValueError("You cannot offer nor request 0 amount of something")
offer_dict_no_ints[asset_id] = amount
if asset_id is not None and wallet is not None:
if callable(getattr(wallet, "get_puzzle_info", None)):
puzzle_driver: PuzzleInfo = wallet.get_puzzle_info(asset_id)
if asset_id in driver_dict and driver_dict[asset_id] != puzzle_driver:
raise ValueError(
f"driver_dict specified {driver_dict[asset_id]}, was expecting {puzzle_driver}"
)
# ignore the case if we're an nft transfering the did owner
if self.check_for_owner_change_in_drivers(puzzle_driver, driver_dict[asset_id]):
driver_dict[asset_id] = puzzle_driver
else:
raise ValueError(
f"driver_dict specified {driver_dict[asset_id]}, was expecting {puzzle_driver}"
)
else:
driver_dict[asset_id] = puzzle_driver
else:
raise ValueError(f"Wallet for asset id {asset_id} is not properly integrated with TradeManager")
potential_special_offer: Optional[Offer] = await self.check_for_special_offer_making(
offer_dict_no_ints,
driver_dict,
fee,
)
if potential_special_offer is not None:
return True, potential_special_offer, None
all_coins: List[Coin] = [c for coins in coins_to_offer.values() for c in coins]
notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments(
requested_payments, all_coins
@@ -397,18 +422,8 @@ class TradeManager:
wallet = self.wallet_state_manager.wallets[id]
else:
wallet = await self.wallet_state_manager.get_wallet_for_asset_id(id.hex())
# This should probably not switch on whether or not we're spending a CAT but it has to for now
# ATTENTION: new_wallets
if wallet.type() == WalletType.CAT:
txs = await wallet.generate_signed_transaction(
[abs(offer_dict[id])],
[Offer.ph()],
fee=fee_left_to_pay,
coins=set(selected_coins),
puzzle_announcements_to_consume=announcements_to_assert,
)
all_transactions.extend(txs)
else:
# This should probably not switch on whether or not we're spending XCH but it has to for now
if wallet.type() == WalletType.STANDARD_WALLET:
tx = await wallet.generate_signed_transaction(
abs(offer_dict[id]),
Offer.ph(),
@@ -417,11 +432,36 @@ class TradeManager:
puzzle_announcements_to_consume=announcements_to_assert,
)
all_transactions.append(tx)
elif wallet.type() == WalletType.NFT:
# This is to generate the tx for specific nft assets, i.e. not using
# wallet_id as the selector which would select any coins from nft_wallet
amounts = [coin.amount for coin in selected_coins]
txs = await wallet.generate_signed_transaction(
# [abs(offer_dict[id])],
amounts,
[Offer.ph()],
fee=fee_left_to_pay,
coins=set(selected_coins),
puzzle_announcements_to_consume=announcements_to_assert,
)
all_transactions.extend(txs)
else:
# ATTENTION: new_wallets
txs = await wallet.generate_signed_transaction(
[abs(offer_dict[id])],
[Offer.ph()],
fee=fee_left_to_pay,
coins=set(selected_coins),
puzzle_announcements_to_consume=announcements_to_assert,
)
all_transactions.extend(txs)
fee_left_to_pay = uint64(0)
transaction_bundles: List[Optional[SpendBundle]] = [tx.spend_bundle for tx in all_transactions]
total_spend_bundle = SpendBundle.aggregate(list(filter(lambda b: b is not None, transaction_bundles)))
total_spend_bundle = SpendBundle.aggregate(
[x.spend_bundle for x in all_transactions if x.spend_bundle is not None]
)
offer = Offer(notarized_payments, total_spend_bundle, driver_dict)
return True, offer, None
@@ -507,13 +547,16 @@ class TradeManager:
removal_dict.setdefault(wallet_id, [])
removal_dict[wallet_id].append(removal)
all_removals: List[bytes32] = [r.name() for removals in removal_dict.values() for r in removals]
for wid, grouped_removals in removal_dict.items():
wallet = self.wallet_state_manager.wallets[wid]
to_puzzle_hash = bytes32([1] * 32) # We use all zeros to be clear not to send here
removal_tree_hash = Program.to([coin_as_list(rem) for rem in grouped_removals]).get_tree_hash()
# We also need to calculate the sent amount
removed: int = sum(c.amount for c in grouped_removals)
change_coins: List[Coin] = addition_dict[wid] if wid in addition_dict else []
potential_change_coins: List[Coin] = addition_dict[wid] if wid in addition_dict else []
change_coins: List[Coin] = [c for c in potential_change_coins if c.parent_coin_info in all_removals]
change_amount: int = sum(c.amount for c in change_coins)
sent_amount: int = removed - change_amount
txs.append(
@@ -542,6 +585,7 @@ class TradeManager:
async def respond_to_offer(self, offer: Offer, fee=uint64(0)) -> Tuple[bool, Optional[TradeRecord], Optional[str]]:
take_offer_dict: Dict[Union[bytes32, int], int] = {}
arbitrage: Dict[Optional[bytes32], int] = offer.arbitrage()
for asset_id, amount in arbitrage.items():
if asset_id is None:
wallet = self.wallet_state_manager.main_wallet
@@ -551,7 +595,7 @@ class TradeManager:
wallet = await self.wallet_state_manager.get_wallet_for_asset_id(asset_id.hex())
if wallet is None and amount < 0:
return False, None, f"Do not have a wallet for asset ID: {asset_id} to fulfill offer"
elif wallet is None:
elif wallet is None or wallet.type() == WalletType.NFT:
key = asset_id
else:
key = int(wallet.id())
@@ -561,15 +605,14 @@ class TradeManager:
valid: bool = await self.check_offer_validity(offer)
if not valid:
return False, None, "This offer is no longer valid"
success, take_offer, error = await self._create_offer_for_ids(take_offer_dict, offer.driver_dict, fee=fee)
if not success or take_offer is None:
return False, None, error
complete_offer = Offer.aggregate([offer, take_offer])
assert complete_offer.is_valid()
final_spend_bundle: SpendBundle = complete_offer.to_valid_spend()
final_spend_bundle: SpendBundle = complete_offer.to_valid_spend()
await self.maybe_create_wallets_for_offer(complete_offer)
tx_records: List[TransactionRecord] = await self.calculate_tx_records_for_offer(complete_offer, True)
@@ -614,3 +657,46 @@ class TradeManager:
await self.wallet_state_manager.add_transaction(tx)
return True, trade_record, None
async def check_for_special_offer_making(
self,
offer_dict: Dict[Optional[bytes32], int],
driver_dict: Dict[bytes32, PuzzleInfo],
fee: uint64 = uint64(0),
) -> Optional[Offer]:
for puzzle_info in driver_dict.values():
if (
puzzle_info.check_type(
[
AssetType.SINGLETON.value,
AssetType.METADATA.value,
AssetType.OWNERSHIP.value,
]
)
and isinstance(puzzle_info.also().also()["transfer_program"], PuzzleInfo) # type: ignore
and puzzle_info.also().also()["transfer_program"].type() # type: ignore
== AssetType.ROYALTY_TRANSFER_PROGRAM.value
):
return await NFTWallet.make_nft1_offer(self.wallet_state_manager, offer_dict, driver_dict, fee)
return None
def check_for_owner_change_in_drivers(self, puzzle_info: PuzzleInfo, driver_info: PuzzleInfo) -> bool:
if puzzle_info.check_type(
[
AssetType.SINGLETON.value,
AssetType.METADATA.value,
AssetType.OWNERSHIP.value,
]
) and driver_info.check_type(
[
AssetType.SINGLETON.value,
AssetType.METADATA.value,
AssetType.OWNERSHIP.value,
]
):
old_owner = driver_info.also().also().info["owner"] # type: ignore
puzzle_info.also().also().info["owner"] = old_owner # type: ignore
if driver_info == puzzle_info:
return True
return False
+68 -22
View File
@@ -12,7 +12,14 @@ from chia.types.coin_spend import CoinSpend
from chia.types.spend_bundle import SpendBundle
from chia.util.bech32m import bech32_decode, bech32_encode, convertbits
from chia.util.ints import uint64
from chia.wallet.outer_puzzles import construct_puzzle, create_asset_id, match_puzzle, solve_puzzle
from chia.wallet.outer_puzzles import (
construct_puzzle,
create_asset_id,
match_puzzle,
solve_puzzle,
get_inner_puzzle,
get_inner_solution,
)
from chia.wallet.payment import Payment
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
from chia.wallet.puzzles.load_clvm import load_clvm
@@ -107,31 +114,70 @@ class Offer:
if asset_id is not None and asset_id not in self.driver_dict:
raise ValueError("Offer does not have enough driver information about the requested payments")
def additions(self) -> List[Coin]:
final_list: List[Coin] = []
for cs in self.bundle.coin_spends:
try:
final_list.extend(cs.additions())
except Exception:
pass
return final_list
def removals(self) -> List[Coin]:
return self.bundle.removals()
def incomplete_spends(self) -> List[CoinSpend]:
final_list: List[CoinSpend] = []
for cs in self.bundle.coin_spends:
try:
cs.additions()
except Exception:
final_list.append(cs)
return final_list
# This method does not get every coin that is being offered, only the `settlement_payment` children
# It's also a little heuristic, but it should get most things
def get_offered_coins(self) -> Dict[Optional[bytes32], List[Coin]]:
offered_coins: Dict[Optional[bytes32], List[Coin]] = {}
for addition in self.bundle.additions():
# Get the parent puzzle
parent_puzzle: Program = list(
filter(lambda cs: cs.coin.name() == addition.parent_coin_info, self.bundle.coin_spends)
)[0].puzzle_reveal.to_program()
OFFER_HASH: bytes32 = OFFER_MOD.get_tree_hash()
for parent_spend in self.bundle.coin_spends:
coins_for_this_spend: List[Coin] = []
parent_puzzle: Program = parent_spend.puzzle_reveal.to_program()
parent_solution: Program = parent_spend.solution.to_program()
additions: List[Coin] = [a for a in parent_spend.additions() if a not in self.bundle.removals()]
# Determine it's TAIL (or lack of)
puzzle_driver = match_puzzle(parent_puzzle)
if puzzle_driver is not None:
asset_id = create_asset_id(puzzle_driver)
offer_ph: bytes32 = construct_puzzle(self.driver_dict[asset_id], OFFER_MOD).get_tree_hash()
inner_puzzle: Optional[Program] = get_inner_puzzle(puzzle_driver, parent_puzzle)
inner_solution: Optional[Program] = get_inner_solution(puzzle_driver, parent_solution)
assert inner_puzzle is not None and inner_solution is not None
conditions: Program = inner_puzzle.run(inner_solution)
for condition in conditions.as_iter():
if condition.first() == 51 and condition.rest().first() == OFFER_HASH:
additions_w_amount: List[Coin] = [
a for a in additions if a.amount == condition.rest().rest().first().as_int()
]
if len(additions_w_amount) == 1:
coins_for_this_spend.append(additions_w_amount[0])
else:
additions_w_amount_and_puzhash: List[Coin] = [
a
for a in additions_w_amount
if a.puzzle_hash
== construct_puzzle(puzzle_driver, OFFER_HASH).get_tree_hash(OFFER_HASH) # type: ignore
]
if len(additions_w_amount_and_puzhash) == 1:
coins_for_this_spend.append(additions_w_amount_and_puzhash[0])
else:
asset_id = None
offer_ph = OFFER_MOD.get_tree_hash()
coins_for_this_spend.extend([a for a in additions if a.puzzle_hash == OFFER_HASH])
# Check if the puzzle_hash matches the hypothetical `settlement_payments` puzzle hash
if addition.puzzle_hash == offer_ph:
if asset_id in offered_coins:
offered_coins[asset_id].append(addition)
else:
offered_coins[asset_id] = [addition]
if coins_for_this_spend != []:
offered_coins.setdefault(asset_id, [])
offered_coins[asset_id].extend(coins_for_this_spend)
return offered_coins
@@ -166,8 +212,8 @@ class Offer:
offered_amounts: Dict[Optional[bytes32], int] = self.get_offered_amounts()
requested_amounts: Dict[Optional[bytes32], int] = self.get_requested_amounts()
def keys_to_strings(dic: Dict[Optional[bytes32], int]) -> Dict[str, int]:
new_dic: Dict[str, int] = {}
def keys_to_strings(dic: Dict[Optional[bytes32], Any]) -> Dict[str, Any]:
new_dic: Dict[str, Any] = {}
for key in dic:
if key is None:
new_dic["xch"] = dic[key]
@@ -184,8 +230,8 @@ class Offer:
# Also mostly for the UI, returns a dictionary of assets and how much of them is pended for this offer
# This method is also imperfect for sufficiently complex spends
def get_pending_amounts(self) -> Dict[str, int]:
all_additions: List[Coin] = self.bundle.additions()
all_removals: List[Coin] = self.bundle.removals()
all_additions: List[Coin] = self.additions()
all_removals: List[Coin] = self.removals()
non_ephemeral_removals: List[Coin] = list(filter(lambda c: c not in all_additions, all_removals))
pending_dict: Dict[str, int] = {}
@@ -209,13 +255,13 @@ class Offer:
# This method returns all of the coins that are being used in the offer (without which it would be invalid)
def get_involved_coins(self) -> List[Coin]:
additions = self.bundle.additions()
return list(filter(lambda c: c not in additions, self.bundle.removals()))
additions = self.additions()
return list(filter(lambda c: c not in additions, self.removals()))
# This returns the non-ephemeral removal that is an ancestor of the specified coin
# This should maybe move to the SpendBundle object at some point
def get_root_removal(self, coin: Coin) -> Coin:
all_removals: Set[Coin] = set(self.bundle.removals())
all_removals: Set[Coin] = set(self.removals())
all_removal_ids: Set[bytes32] = {c.name() for c in all_removals}
non_ephemeral_removals: Set[Coin] = {
c for c in all_removals if c.parent_coin_info not in {r.name() for r in all_removals}
+12
View File
@@ -5,12 +5,24 @@ from typing import List
from chia.wallet.puzzles.load_clvm import load_clvm
from chia.wallet.puzzles import p2_delegated_puzzle_or_hidden_puzzle as standard_puzzle
from chia.wallet.puzzles.cat_loader import CAT_MOD
from chia.wallet.nft_wallet.nft_puzzles import (
SINGLETON_TOP_LAYER_MOD,
NFT_STATE_LAYER_MOD,
NFT_OWNERSHIP_LAYER,
NFT_METADATA_UPDATER,
NFT_TRANSFER_PROGRAM_DEFAULT,
)
OFFER_MOD = load_clvm("settlement_payments.clvm")
ZDICT = [
bytes(standard_puzzle.MOD) + bytes(CAT_MOD),
bytes(OFFER_MOD),
bytes(SINGLETON_TOP_LAYER_MOD)
+ bytes(NFT_STATE_LAYER_MOD)
+ bytes(NFT_OWNERSHIP_LAYER)
+ bytes(NFT_METADATA_UPDATER)
+ bytes(NFT_TRANSFER_PROGRAM_DEFAULT),
# more dictionaries go here
]
+1 -1
View File
@@ -17,7 +17,7 @@ class WalletType(IntEnum):
CUSTODY = 5
CAT = 6
RECOVERABLE = 7
DISTRIBUTED_ID = 8
DECENTRALIZED_ID = 8
POOLING_WALLET = 9
NFT = 10
DATA_LAYER = 11
+143
View File
@@ -0,0 +1,143 @@
import json
from typing import List, Optional, Type, TypeVar
import aiosqlite
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.db_wrapper import DBWrapper
from chia.util.ints import uint32
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.nft_wallet.nft_info import DEFAULT_STATUS, IN_TRANSACTION_STATUS, NFTCoinInfo
_T_WalletNftStore = TypeVar("_T_WalletNftStore", bound="WalletNftStore")
class WalletNftStore:
"""
WalletNftStore keeps track of all user created NFTs and necessary smart-contract data
"""
db_connection: aiosqlite.Connection
db_wrapper: DBWrapper
@classmethod
async def create(cls: Type[_T_WalletNftStore], db_wrapper: DBWrapper) -> _T_WalletNftStore:
self = cls()
self.db_wrapper = db_wrapper
self.db_connection = db_wrapper.db
await self.db_connection.execute(
(
"CREATE TABLE IF NOT EXISTS users_nfts("
" nft_id text PRIMARY KEY,"
" nft_coin_id text,"
" wallet_id int,"
" did_id text,"
" coin text,"
" lineage_proof text,"
" mint_height bigint,"
" status text,"
" full_puzzle blob)"
)
)
await self.db_connection.execute("CREATE INDEX IF NOT EXISTS nft_coin_id on users_nfts(nft_coin_id)")
await self.db_connection.execute("CREATE INDEX IF NOT EXISTS nft_wallet_id on users_nfts(wallet_id)")
await self.db_connection.execute("CREATE INDEX IF NOT EXISTS nft_did_id on users_nfts(did_id)")
await self.db_connection.commit()
return self
async def _clear_database(self) -> None:
cursor = await self.db_connection.execute("DELETE FROM users_nfts")
await cursor.close()
await self.db_connection.commit()
async def delete_nft(self, nft_id: bytes32, in_transaction: bool = False) -> None:
if not in_transaction:
await self.db_wrapper.lock.acquire()
try:
cursor = await self.db_connection.execute(f"DELETE FROM users_nfts where nft_id='{nft_id.hex()}'")
await cursor.close()
finally:
if not in_transaction:
await self.db_connection.commit()
self.db_wrapper.lock.release()
async def save_nft(
self, wallet_id: uint32, did_id: Optional[bytes32], nft_coin_info: NFTCoinInfo, in_transaction: bool = False
) -> None:
if not in_transaction:
await self.db_wrapper.lock.acquire()
try:
cursor = await self.db_connection.execute(
"INSERT or REPLACE INTO users_nfts VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
nft_coin_info.nft_id.hex(),
nft_coin_info.coin.name().hex(),
int(wallet_id),
did_id.hex() if did_id else None,
json.dumps(nft_coin_info.coin.to_json_dict()),
json.dumps(nft_coin_info.lineage_proof.to_json_dict())
if nft_coin_info.lineage_proof is not None
else None,
int(nft_coin_info.mint_height),
IN_TRANSACTION_STATUS if nft_coin_info.pending_transaction else DEFAULT_STATUS,
bytes(nft_coin_info.full_puzzle),
),
)
await cursor.close()
finally:
if not in_transaction:
await self.db_connection.commit()
self.db_wrapper.lock.release()
async def get_nft_list(
self, wallet_id: Optional[uint32] = None, did_id: Optional[bytes32] = None
) -> List[NFTCoinInfo]:
sql: str = "SELECT nft_id, coin, lineage_proof, mint_height, status, full_puzzle from users_nfts"
if wallet_id is not None and did_id is None:
sql += f" where wallet_id={wallet_id}"
if wallet_id is None and did_id is not None:
sql += f" where did_id='{did_id.hex()}'"
if wallet_id is not None and did_id is not None:
sql += f" where did_id='{did_id.hex()}' and wallet_id={wallet_id}"
cursor = await self.db_connection.execute(sql)
rows = await cursor.fetchall()
await cursor.close()
result = []
for row in rows:
result.append(
NFTCoinInfo(
bytes32.from_hexstr(row[0]),
Coin.from_json_dict(json.loads(row[1])),
None if row[2] is None else LineageProof.from_json_dict(json.loads(row[2])),
Program.from_bytes(row[5]),
uint32(row[3]),
row[4] == IN_TRANSACTION_STATUS,
)
)
return result
async def get_nft_by_id(self, nft_id: bytes32) -> Optional[NFTCoinInfo]:
cursor = await self.db_connection.execute(
"SELECT nft_id, coin, lineage_proof, mint_height, status, full_puzzle from users_nfts WHERE nft_id=?",
(nft_id.hex(),),
)
row = await cursor.fetchone()
await cursor.close()
if row is None:
return None
return NFTCoinInfo(
bytes32.from_hexstr(row[0]),
Coin.from_json_dict(json.loads(row[1])),
None if row[2] is None else LineageProof.from_json_dict(json.loads(row[2])),
Program.from_bytes(row[5]),
uint32(row[3]),
row[4] == IN_TRANSACTION_STATUS,
)
async def close(self) -> None:
await self.db_connection.close()
+6 -2
View File
@@ -1457,13 +1457,17 @@ class WalletNode:
if len(all_nodes.keys()) == 0:
raise ValueError("Not connected to the full node")
# Use supplied if provided, prioritize trusted otherwise
synced_peers = [node for node in all_nodes.values() if node.peer_node_id in self.synced_peers]
if peer is None:
for node in list(all_nodes.values()):
for node in synced_peers:
if self.is_trusted(node):
peer = node
break
if peer is None:
peer = list(all_nodes.values())[0]
if len(synced_peers) > 0:
peer = synced_peers[0]
else:
peer = list(all_nodes.values())[0]
assert peer is not None
msg = wallet_protocol.RegisterForCoinUpdates(coin_names, uint32(0))
+113 -39
View File
@@ -28,6 +28,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend
from chia.types.full_block import FullBlock
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.util.bech32m import encode_puzzle_hash
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
@@ -39,10 +40,13 @@ from chia.wallet.cat_wallet.cat_utils import construct_cat_puzzle, match_cat_puz
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_info import DID_HRP
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.nft_info import NFTWalletInfo
from chia.wallet.nft_wallet.nft_puzzles import get_metadata_and_phs, get_new_owner_did
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.puzzle_drivers import PuzzleInfo
@@ -63,6 +67,7 @@ from chia.wallet.wallet_coin_record import WalletCoinRecord
from chia.wallet.wallet_coin_store import WalletCoinStore
from chia.wallet.wallet_info import WalletInfo
from chia.wallet.wallet_interested_store import WalletInterestedStore
from chia.wallet.wallet_nft_store import WalletNftStore
from chia.wallet.wallet_pool_store import WalletPoolStore
from chia.wallet.wallet_puzzle_store import WalletPuzzleStore
from chia.wallet.wallet_sync_store import WalletSyncStore
@@ -77,6 +82,7 @@ class WalletStateManager:
tx_store: WalletTransactionStore
puzzle_store: WalletPuzzleStore
user_store: WalletUserStore
nft_store: WalletNftStore
action_store: WalletActionStore
basic_store: KeyValStore
@@ -153,6 +159,7 @@ class WalletStateManager:
self.tx_store = await WalletTransactionStore.create(self.db_wrapper)
self.puzzle_store = await WalletPuzzleStore.create(self.db_wrapper)
self.user_store = await WalletUserStore.create(self.db_wrapper)
self.nft_store = await WalletNftStore.create(self.db_wrapper)
self.action_store = await WalletActionStore.create(self.db_wrapper)
self.basic_store = await KeyValStore.create(self.db_wrapper)
self.trade_manager = await TradeManager.create(self, self.db_wrapper)
@@ -204,7 +211,7 @@ class WalletStateManager:
)
elif wallet_info.type == WalletType.RATE_LIMITED:
wallet = await RLWallet.create(self, wallet_info)
elif wallet_info.type == WalletType.DISTRIBUTED_ID:
elif wallet_info.type == WalletType.DECENTRALIZED_ID:
wallet = await DIDWallet.create(
self,
self.main_wallet,
@@ -271,9 +278,13 @@ class WalletStateManager:
for wallet_id in targets:
target_wallet = self.wallets[wallet_id]
if not hasattr(target_wallet, "puzzle_for_pk"):
self.log.debug("Skipping wallet %s as no derivation paths required", wallet_id)
continue
last: Optional[uint32] = await self.puzzle_store.get_last_derivation_path_for_wallet(wallet_id)
self.log.debug(
"Fetched last record for wallet %r: %s (from_zero=%r, unused=%r)", wallet_id, last, from_zero, unused
)
start_index = 0
derivation_paths: List[DerivationRecord] = []
@@ -333,10 +344,11 @@ class WalletStateManager:
[record.wallet_id for record in derivation_paths],
in_transaction,
)
if unused > 0:
await self.puzzle_store.set_used_up_to(uint32(unused - 1), in_transaction)
async def update_wallet_puzzle_hashes(self, wallet_id) -> None:
async def update_wallet_puzzle_hashes(self, wallet_id, in_transaction=False) -> None:
derivation_paths: List[DerivationRecord] = []
target_wallet = self.wallets[wallet_id]
last: Optional[uint32] = await self.puzzle_store.get_last_derivation_path_for_wallet(wallet_id)
@@ -364,7 +376,7 @@ class WalletStateManager:
False,
)
)
await self.puzzle_store.add_derivation_paths(derivation_paths)
await self.puzzle_store.add_derivation_paths(derivation_paths, in_transaction=in_transaction)
async def get_unused_derivation_record(
self, wallet_id: uint32, in_transaction=False, hardened=False
@@ -449,6 +461,9 @@ class WalletStateManager:
self.pending_tx_callback()
async def synced(self):
if len(self.server.get_full_node_connections()) == 0:
return False
latest = await self.blockchain.get_peak_block()
if latest is None:
return False
@@ -707,8 +722,21 @@ class WalletStateManager:
self.log.warning(f"Could not find the launch coin with ID: {launch_id}")
return None, None
launch_coin: CoinState = response[0]
origin_coin = launch_coin.coin
for wallet in self.wallets.values():
if (
wallet.type() == WalletType.DECENTRALIZED_ID
and origin_coin.name() == wallet.did_info.origin_coin.name()
):
return wallet.id(), wallet.type()
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()}"
self,
self.main_wallet,
launch_coin.coin,
did_puzzle,
coin_spend,
f"DID {encode_puzzle_hash(launch_id, DID_HRP)}",
)
wallet_id = did_wallet.id()
wallet_type = WalletType(did_wallet.type())
@@ -726,30 +754,74 @@ class WalletStateManager:
"""
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
# DID ID determines which NFT wallet should process the NFT
new_did_id = None
old_did_id = None
# P2 puzzle hash determines if we should ignore the NFT
old_p2_puzhash = uncurried_nft.p2_puzzle.get_tree_hash()
metadata, new_p2_puzhash = get_metadata_and_phs(
uncurried_nft,
coin_spend.solution,
)
if uncurried_nft.supports_did:
new_did_id = get_new_owner_did(uncurried_nft, coin_spend.solution.to_program())
old_did_id = uncurried_nft.owner_did
if new_did_id is None:
new_did_id = old_did_id
if new_did_id == b"":
new_did_id = None
self.log.debug(
"Handling NFT: %s old DID:%s, new DID:%s, old P2:%s, new P2:%s",
coin_spend,
old_did_id,
new_did_id,
old_p2_puzhash,
new_p2_puzhash,
)
new_derivation_record: Optional[
DerivationRecord
] = await self.puzzle_store.get_derivation_record_for_puzzle_hash(new_p2_puzhash)
old_derivation_record: Optional[
DerivationRecord
] = await self.puzzle_store.get_derivation_record_for_puzzle_hash(old_p2_puzhash)
if new_derivation_record is None and old_derivation_record is None:
self.log.debug(
"Cannot find a P2 puzzle hash for NFT:%s, this NFT belongs to others.",
uncurried_nft.singleton_launcher_id.hex(),
)
wallet_id = uint32(nft_wallet.wallet_id)
wallet_type = WalletType.NFT
return wallet_id, wallet_type
for wallet_info in await self.get_all_wallet_info_entries(wallet_type=WalletType.NFT):
nft_wallet_info: NFTWalletInfo = NFTWalletInfo.from_json_dict(json.loads(wallet_info.data))
if nft_wallet_info.did_id == old_did_id:
self.log.info(
"Removing old NFT, NFT_ID:%s, DID_ID:%s",
uncurried_nft.singleton_launcher_id.hex(),
old_did_id,
)
nft_wallet: NFTWallet = self.wallets[wallet_info.id]
await nft_wallet.remove_coin(coin_spend.coin, in_transaction=True)
if nft_wallet_info.did_id == new_did_id:
self.log.info(
"Adding new NFT, NFT_ID:%s, DID_ID:%s",
uncurried_nft.singleton_launcher_id.hex(),
new_did_id,
)
wallet_id = wallet_info.id
wallet_type = WalletType.NFT
if wallet_id is None and new_derivation_record:
# Cannot find an existed NFT wallet for the new NFT
self.log.info(
"Cannot find a NFT wallet for NFT_ID: %s DID_ID: %s, creating a new one.",
uncurried_nft.singleton_launcher_id,
new_did_id,
)
new_nft_wallet: NFTWallet = await NFTWallet.create_new_nft_wallet(
self, self.main_wallet, did_id=new_did_id, name="NFT Wallet", in_transaction=True
)
wallet_id = uint32(new_nft_wallet.wallet_id)
wallet_type = WalletType.NFT
return wallet_id, wallet_type
async def new_coin_state(
@@ -760,7 +832,6 @@ class WalletStateManager:
in_transaction: bool = False,
) -> None:
# TODO: add comment about what this method does
# Input states should already be sorted by cs_height, with reorgs at the beginning
curr_h = -1
for c_state in coin_states:
@@ -1224,7 +1295,7 @@ class WalletStateManager:
)
await self.coin_store.add_coin_record(coin_record_1)
if wallet_type == WalletType.CAT or wallet_type == WalletType.DISTRIBUTED_ID:
if wallet_type == WalletType.CAT or wallet_type == WalletType.DECENTRALIZED_ID:
wallet = self.wallets[wallet_id]
await wallet.coin_added(coin, height)
@@ -1351,6 +1422,10 @@ class WalletStateManager:
if wallet.type() == WalletType.CAT:
if bytes(wallet.cat_info.limitations_program_hash).hex() == asset_id:
return wallet
elif wallet.type() == WalletType.NFT:
for nft_coin in wallet.my_nft_coins:
if nft_coin.nft_id.hex() == asset_id:
return wallet
return None
async def get_wallet_for_puzzle_info(self, puzzle_driver: PuzzleInfo):
@@ -1363,14 +1438,13 @@ class WalletStateManager:
async def create_wallet_for_puzzle_info(self, puzzle_driver: PuzzleInfo, name=None, in_transaction=False):
if AssetType(puzzle_driver.type()) in self.asset_to_wallet_map:
async with self.lock:
await self.asset_to_wallet_map[AssetType(puzzle_driver.type())].create_from_puzzle_info(
self,
self.main_wallet,
puzzle_driver,
name,
in_transaction,
)
await self.asset_to_wallet_map[AssetType(puzzle_driver.type())].create_from_puzzle_info(
self,
self.main_wallet,
puzzle_driver,
name,
in_transaction,
)
async def add_new_wallet(self, wallet: Any, wallet_id: int, create_puzzle_hashes=True, in_transaction=False):
self.wallets[uint32(wallet_id)] = wallet
+2 -2
View File
@@ -180,10 +180,10 @@ class WalletWeightProofHandler:
overflow = 0
count = 0
for idx, new_ses in enumerate(new_wp.sub_epochs):
for new_ses in new_wp.sub_epochs:
if new_ses.reward_chain_hash in old_ses:
count += 1
overflow += new_ses.num_blocks_overflow
overflow = new_ses.num_blocks_overflow
continue
else:
break
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -5
View File
@@ -45,12 +45,11 @@ wallet_program_files = set(
"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_metadata_updater_updateable.clvm",
"chia/wallet/puzzles/nft_state_layer.clvm",
"chia/wallet/puzzles/nft_ownership_layer.clvm",
"chia/wallet/puzzles/nft_ownership_transfer_program_one_way_claim_with_royalties.clvm",
"chia/wallet/puzzles/database_offer.clvm",
"chia/wallet/puzzles/database_layer.clvm",
"chia/wallet/puzzles/singleton_top_layer_atari_only.clvm",
@@ -168,7 +167,7 @@ class TestClvmCompilation(TestCase):
self.assertEqual(
s.get_tree_hash().hex(),
existing_sha,
msg=f"Checked-in shatree hash file does not match shatree hash of loaded SerializedProgram: {prog_path}", # noqa
msg=f"Checked-in shatree hash file does not match hash of loaded SerializedProgram: {prog_path}",
)
self.assertEqual(
p.get_tree_hash().hex(),
+16 -9
View File
@@ -14,11 +14,7 @@ from chia.util.errors import Err
from chia.util.ints import uint64
from chia.consensus.default_constants import DEFAULT_CONSTANTS
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.puzzles import (
p2_conditions,
p2_delegated_puzzle_or_hidden_puzzle,
singleton_top_layer,
)
from chia.wallet.puzzles import p2_conditions, p2_delegated_puzzle_or_hidden_puzzle
from tests.util.key_tool import KeyTool
from tests.clvm.test_puzzles import (
public_key_for_index,
@@ -31,6 +27,8 @@ from chia.clvm.spend_sim import SpendSim, SimClient
This test suite aims to test:
- chia.wallet.puzzles.singleton_top_layer.py
- chia.wallet.puzzles.singleton_top_layer.clvm
- chia.wallet.puzzles.singleton_top_layer_v1_1.py
- chia.wallet.puzzles.singleton_top_layer_v1_1.clvm
- chia.wallet.puzzles.p2_singleton.clvm
- chia.wallet.puzzles.p2_singleton_or_delayed_puzhash.clvm
"""
@@ -83,14 +81,23 @@ class TestSingleton:
raise AssertionError(fail_msg)
@pytest.mark.asyncio
async def test_singleton_top_layer(self):
@pytest.mark.parametrize("version", [0, 1])
async def test_singleton_top_layer(self, version):
try:
# START TESTS
# Generate starting info
key_lookup = KeyTool()
pk: G1Element = public_key_for_index(1, key_lookup)
starting_puzzle: Program = p2_delegated_puzzle_or_hidden_puzzle.puzzle_for_pk(pk) # noqa
adapted_puzzle: Program = singleton_top_layer.adapt_inner_to_singleton(starting_puzzle) # noqa
if version == 0:
from chia.wallet.puzzles import singleton_top_layer
adapted_puzzle: Program = singleton_top_layer.adapt_inner_to_singleton(starting_puzzle) # noqa
else:
from chia.wallet.puzzles import singleton_top_layer_v1_1 as singleton_top_layer
adapted_puzzle = starting_puzzle
adapted_puzzle_hash: bytes32 = adapted_puzzle.get_tree_hash()
# Get our starting standard coin created
@@ -436,7 +443,7 @@ class TestSingleton:
)
# Now try a perfectly innocent spend
evil_coin: Coin = (await sim.all_non_reward_coins())[0]
evil_coin: Coin = next(filter(lambda c: c.amount == 2, (await sim.all_non_reward_coins())))
delegated_puzzle: Program = Program.to(
(
1,
@@ -473,7 +480,7 @@ class TestSingleton:
evil_coin,
delegated_puzzle,
[evil_coinsol],
ex_error=Err.ASSERT_MY_COIN_ID_FAILED,
ex_error=Err.ASSERT_MY_COIN_ID_FAILED if version == 0 else Err.ASSERT_MY_AMOUNT_FAILED,
fail_msg="This coin is even!",
)
+5 -1
View File
@@ -20,10 +20,14 @@ from tests.time_out_assert import time_out_assert
log = logging.getLogger(__name__)
async def disconnect_all_and_reconnect(server: ChiaServer, reconnect_to: ChiaServer, self_hostname: str) -> bool:
async def disconnect_all(server: ChiaServer) -> None:
cons = list(server.all_connections.values())[:]
for con in cons:
await con.close()
async def disconnect_all_and_reconnect(server: ChiaServer, reconnect_to: ChiaServer, self_hostname: str) -> bool:
await disconnect_all(server)
return await server.start_client(PeerInfo(self_hostname, uint16(reconnect_to._port)), None)
@@ -0,0 +1,66 @@
from typing import Optional
from clvm_tools.binutils import disassemble
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.util.ints import uint64
from chia.wallet.cat_wallet.cat_utils import CAT_MOD, construct_cat_puzzle
from chia.wallet.outer_puzzles import (
construct_puzzle,
create_asset_id,
get_inner_puzzle,
get_inner_solution,
match_puzzle,
solve_puzzle,
)
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
def test_cat_outer_puzzle() -> None:
ACS = Program.to(1)
tail = bytes32([0] * 32)
cat_puzzle: Program = construct_cat_puzzle(CAT_MOD, tail, ACS)
double_cat_puzzle: Program = construct_cat_puzzle(CAT_MOD, tail, cat_puzzle)
cat_driver: Optional[PuzzleInfo] = match_puzzle(double_cat_puzzle)
assert cat_driver is not None
assert cat_driver.type() == "CAT"
assert cat_driver["tail"] == tail
inside_cat_driver: Optional[PuzzleInfo] = cat_driver.also()
assert inside_cat_driver is not None
assert inside_cat_driver.type() == "CAT"
assert inside_cat_driver["tail"] == tail
assert construct_puzzle(cat_driver, ACS) == double_cat_puzzle
assert get_inner_puzzle(cat_driver, double_cat_puzzle) == ACS
assert create_asset_id(cat_driver) == tail
# Set up for solve
parent_coin = Coin(tail, double_cat_puzzle.get_tree_hash(), uint64(100))
child_coin = Coin(parent_coin.name(), double_cat_puzzle.get_tree_hash(), uint64(100))
parent_spend = CoinSpend(parent_coin, double_cat_puzzle.to_serialized_program(), Program.to([]))
child_coin_as_hex: str = (
"0x" + child_coin.parent_coin_info.hex() + child_coin.puzzle_hash.hex() + bytes(child_coin.amount).hex()
)
parent_spend_as_hex: str = "0x" + bytes(parent_spend).hex()
inner_solution = Program.to([[51, ACS.get_tree_hash(), 100]])
solution: Program = solve_puzzle(
cat_driver,
Solver(
{
"coin": child_coin_as_hex,
"parent_spend": parent_spend_as_hex,
"siblings": "(" + child_coin_as_hex + ")",
"sibling_spends": "(" + parent_spend_as_hex + ")",
"sibling_puzzles": "(" + disassemble(ACS) + ")", # type: ignore
"sibling_solutions": "(" + disassemble(inner_solution) + ")", # type: ignore
}
),
ACS,
inner_solution,
)
double_cat_puzzle.run(solution)
assert get_inner_solution(cat_driver, solution) == inner_solution
+171 -68
View File
@@ -23,8 +23,12 @@ async def get_wallet_num(wallet_manager):
class TestDIDWallet:
@pytest.mark.parametrize(
"trusted",
[True, False],
)
@pytest.mark.asyncio
async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes):
async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes, trusted):
num_blocks = 5
full_nodes, wallets = three_wallet_nodes
full_node_api = full_nodes[0]
@@ -39,7 +43,20 @@ class TestDIDWallet:
ph = await wallet_0.get_new_puzzlehash()
ph1 = await wallet_1.get_new_puzzlehash()
ph2 = await wallet_2.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()
}
wallet_node_2.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"] = {}
wallet_node_2.config["trusted_peers"] = {}
await server_0.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
await server_1.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
await server_2.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
@@ -67,7 +84,9 @@ 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_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())
@@ -86,7 +105,9 @@ 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_list = await wallet_node_1.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())
@@ -121,7 +142,6 @@ class TestDIDWallet:
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):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
@@ -139,7 +159,6 @@ class TestDIDWallet:
pubkey,
test_message_spend_bundle,
)
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())
@@ -172,8 +191,12 @@ class TestDIDWallet:
await time_out_assert(45, did_wallet_2.get_confirmed_balance, 0)
await time_out_assert(45, did_wallet_2.get_unconfirmed_balance, 0)
@pytest.mark.parametrize(
"trusted",
[True, False],
)
@pytest.mark.asyncio
async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_wallet_nodes):
async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_wallet_nodes, trusted):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
@@ -184,7 +207,16 @@ class TestDIDWallet:
wallet2 = wallet_node_2.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
if trusted:
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()
}
else:
wallet_node.config["trusted_peers"] = {}
wallet_node_2.config["trusted_peers"] = {}
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)
@@ -205,7 +237,7 @@ class TestDIDWallet:
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_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())
@@ -224,7 +256,9 @@ 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_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())
@@ -244,7 +278,9 @@ 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_list = await wallet_node_2.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())
@@ -268,10 +304,11 @@ class TestDIDWallet:
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_did_inner_hash()
new_ph = did_wallet_4.did_info.temp_puzhash
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())
@@ -294,7 +331,8 @@ class TestDIDWallet:
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_4.get_confirmed_balance, 0)
await time_out_assert(15, did_wallet_4.get_unconfirmed_balance, 0)
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()
@@ -311,8 +349,12 @@ class TestDIDWallet:
await time_out_assert(15, did_wallet_3.get_confirmed_balance, 0)
await time_out_assert(15, did_wallet_3.get_unconfirmed_balance, 0)
@pytest.mark.parametrize(
"trusted",
[True, False],
)
@pytest.mark.asyncio
async def test_did_recovery_with_empty_set(self, self_hostname, two_wallet_nodes):
async def test_did_recovery_with_empty_set(self, self_hostname, two_wallet_nodes, trusted):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
@@ -322,7 +364,16 @@ class TestDIDWallet:
wallet = wallet_node.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
if trusted:
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()
}
else:
wallet_node.config["trusted_peers"] = {}
wallet_node_2.config["trusted_peers"] = {}
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)
@@ -343,7 +394,7 @@ 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_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())
@@ -355,7 +406,6 @@ class TestDIDWallet:
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
try:
@@ -368,8 +418,12 @@ class TestDIDWallet:
else:
assert False
@pytest.mark.parametrize(
"trusted",
[True, False],
)
@pytest.mark.asyncio
async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes):
async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, trusted):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
@@ -379,7 +433,16 @@ class TestDIDWallet:
wallet = wallet_node.wallet_state_manager.main_wallet
wallet2 = wallet_node_2.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
if trusted:
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()
}
else:
wallet_node.config["trusted_peers"] = {}
wallet_node_2.config["trusted_peers"] = {}
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):
@@ -398,7 +461,7 @@ 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_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())
@@ -414,7 +477,9 @@ 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_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())
@@ -457,6 +522,7 @@ class TestDIDWallet:
pubkey = (
await did_wallet_3.wallet_state_manager.get_unused_derivation_record(did_wallet_3.wallet_info.id)
).pubkey
await time_out_assert(15, did_wallet.get_confirmed_balance, 101)
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())
@@ -533,9 +599,14 @@ class TestDIDWallet:
"with_recovery",
[True, False],
)
@pytest.mark.parametrize(
"trusted",
[True, False],
)
@pytest.mark.asyncio
async def test_did_transfer(self, two_wallet_nodes, with_recovery):
async def test_did_transfer(self, two_wallet_nodes, with_recovery, trusted):
num_blocks = 5
fee = uint64(1000)
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
server_1 = full_node_api.server
@@ -545,13 +616,16 @@ class TestDIDWallet:
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()
}
if trusted:
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()
}
else:
wallet_node.config["trusted_peers"] = {}
wallet_node_2.config["trusted_peers"] = {}
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
await server_3.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
@@ -575,20 +649,9 @@ class TestDIDWallet:
[bytes(ph)],
uint64(1),
{"Twitter": "Test", "GitHub": "测试"},
fee=fee,
)
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()
)
@@ -597,12 +660,29 @@ class TestDIDWallet:
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)
await time_out_assert(15, wallet.get_confirmed_balance, 7999999998899)
await time_out_assert(15, wallet.get_unconfirmed_balance, 7999999998899)
# Transfer DID
new_puzhash = await wallet2.get_new_puzzlehash()
await did_wallet_1.transfer_did(new_puzhash, fee, with_recovery)
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))
await time_out_assert(15, wallet.get_confirmed_balance, 7999999997899)
await time_out_assert(15, wallet.get_unconfirmed_balance, 7999999997899)
# 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),
lambda w: (w.type == WalletType.DECENTRALIZED_ID),
await wallet_node_2.wallet_state_manager.get_all_wallet_info_entries(),
)
)
@@ -611,12 +691,16 @@ class TestDIDWallet:
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)
metadata = json.loads(did_wallet_2.did_info.metadata)
assert metadata["Twitter"] == "Test"
assert metadata["GitHub"] == "测试"
@pytest.mark.parametrize(
"trusted",
[True, False],
)
@pytest.mark.asyncio
async def test_update_recovery_list(self, two_wallet_nodes):
async def test_update_recovery_list(self, two_wallet_nodes, trusted):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
@@ -626,13 +710,16 @@ class TestDIDWallet:
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()
}
if trusted:
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()
}
else:
wallet_node.config["trusted_peers"] = {}
wallet_node_2.config["trusted_peers"] = {}
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
await server_3.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
@@ -652,7 +739,9 @@ class TestDIDWallet:
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_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 wallet.get_new_puzzlehash()
@@ -670,24 +759,33 @@ class TestDIDWallet:
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.parametrize(
"trusted",
[True, False],
)
@pytest.mark.asyncio
async def test_update_metadata(self, two_wallet_nodes):
async def test_update_metadata(self, two_wallet_nodes, trusted):
num_blocks = 5
fee = uint64(1000)
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
wallet1 = 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()
}
ph1 = await wallet1.get_new_puzzlehash()
if trusted:
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()
}
else:
wallet_node.config["trusted_peers"] = {}
wallet_node_2.config["trusted_peers"] = {}
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
await server_3.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
@@ -705,23 +803,28 @@ class TestDIDWallet:
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), []
wallet_node.wallet_state_manager, wallet, uint64(101), [], fee=fee
)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
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 wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
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 time_out_assert(15, wallet.get_confirmed_balance, 7999999998899)
await time_out_assert(15, wallet.get_unconfirmed_balance, 7999999998899)
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()
await did_wallet_1.create_update_spend(fee)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
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 time_out_assert(15, wallet.get_confirmed_balance, 7999999997899)
await time_out_assert(15, wallet.get_unconfirmed_balance, 7999999997899)
assert did_wallet_1.did_info.metadata.find("Twitter") > 0
File diff suppressed because it is too large Load Diff
-93
View File
@@ -1,93 +0,0 @@
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()
)
@@ -0,0 +1,365 @@
import itertools
from typing import List, Tuple
import pytest
from blspy import G2Element
from chia.clvm.spend_sim import SimClient, SpendSim
from chia.types.announcement import Announcement
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.mempool_inclusion_status import MempoolInclusionStatus
from chia.types.spend_bundle import SpendBundle
from chia.util.errors import Err
from chia.wallet.nft_wallet.nft_puzzles import (
NFT_METADATA_UPDATER,
NFT_TRANSFER_PROGRAM_DEFAULT,
construct_ownership_layer,
create_nft_layer_puzzle_with_curry_params,
metadata_to_program,
)
ACS = Program.to(1)
ACS_PH = ACS.get_tree_hash()
@pytest.mark.asyncio()
@pytest.mark.parametrize("metadata_updater", ["default"])
async def test_state_layer(setup_sim: Tuple[SpendSim, SimClient], metadata_updater: str) -> None:
sim, sim_client = setup_sim
try:
if metadata_updater == "default":
METADATA: Program = metadata_to_program(
{
b"u": ["hey hey"],
b"lu": ["You have no permissions grr"],
b"mu": ["This but off chain"],
b"foo": ["Can't update this"],
}
)
METADATA_UPDATER: Program = NFT_METADATA_UPDATER
else:
# TODO: Add test for updateable
return
METADATA_UPDATER_PUZZLE_HASH: bytes32 = METADATA_UPDATER.get_tree_hash()
state_layer_puzzle: Program = create_nft_layer_puzzle_with_curry_params(
METADATA, METADATA_UPDATER_PUZZLE_HASH, ACS
)
state_layer_ph: bytes32 = state_layer_puzzle.get_tree_hash()
await sim.farm_block(state_layer_ph)
state_layer_coin = (
await sim_client.get_coin_records_by_puzzle_hash(state_layer_ph, include_spent_coins=False)
)[0].coin
generic_spend = CoinSpend(
state_layer_coin,
state_layer_puzzle,
Program.to([[[51, ACS_PH, 1]]]),
)
generic_bundle = SpendBundle([generic_spend], G2Element())
result = await sim_client.push_tx(generic_bundle)
assert result == (MempoolInclusionStatus.SUCCESS, None)
await sim.farm_block()
if metadata_updater == "default":
metadata_updater_solutions: List[Program] = [
Program.to((b"u", "update")),
Program.to((b"lu", "update")),
Program.to((b"mu", "update")),
Program.to((b"foo", "update")),
]
expected_metadatas: List[Program] = [
metadata_to_program(
{
b"u": ["update", "hey hey"],
b"lu": ["You have no permissions grr"],
b"mu": ["This but off chain"],
b"foo": ["Can't update this"],
}
),
metadata_to_program(
{
b"u": ["update", "hey hey"],
b"lu": ["update", "You have no permissions grr"],
b"mu": ["This but off chain"],
b"foo": ["Can't update this"],
}
),
metadata_to_program(
{
b"u": ["update", "hey hey"],
b"lu": ["update", "You have no permissions grr"],
b"mu": ["update", "This but off chain"],
b"foo": ["Can't update this"],
}
),
metadata_to_program(
{ # no change
b"u": ["update", "hey hey"],
b"lu": ["update", "You have no permissions grr"],
b"mu": ["update", "This but off chain"],
b"foo": ["Can't update this"],
}
),
]
else:
return
for solution, metadata in zip(metadata_updater_solutions, expected_metadatas):
state_layer_coin = (
await sim_client.get_coin_records_by_parent_ids([state_layer_coin.name()], include_spent_coins=False)
)[0].coin
update_spend = CoinSpend(
state_layer_coin,
state_layer_puzzle,
Program.to(
[
[
[51, ACS_PH, 1],
[-24, METADATA_UPDATER, solution],
]
]
),
)
update_bundle = SpendBundle([update_spend], G2Element())
result = await sim_client.push_tx(update_bundle)
assert result == (MempoolInclusionStatus.SUCCESS, None)
await sim.farm_block()
state_layer_puzzle = create_nft_layer_puzzle_with_curry_params(metadata, METADATA_UPDATER_PUZZLE_HASH, ACS)
finally:
await sim.close() # type: ignore
@pytest.mark.asyncio()
async def test_ownership_layer(setup_sim: Tuple[SpendSim, SimClient]) -> None:
sim, sim_client = setup_sim
try:
TARGET_OWNER = bytes32([0] * 32)
TARGET_TP = Program.to([8]) # (x)
# (a (i 11 (q 4 19 (c 43 (q ()))) (q 8)) 1) or
# (mod (_ _ solution) (if solution (list (f solution) (f (r solution)) ()) (x)))
transfer_program = Program.to([2, [3, 11, [1, 4, 19, [4, 43, [1, []]]], [1, 8]], 1])
ownership_puzzle: Program = construct_ownership_layer(
None,
transfer_program,
ACS,
)
ownership_ph: bytes32 = ownership_puzzle.get_tree_hash()
await sim.farm_block(ownership_ph)
ownership_coin = (await sim_client.get_coin_records_by_puzzle_hash(ownership_ph, include_spent_coins=False))[
0
].coin
generic_spend = CoinSpend(
ownership_coin,
ownership_puzzle,
Program.to([[[51, ACS_PH, 1], [-10, [], []]]]),
)
generic_bundle = SpendBundle([generic_spend], G2Element())
result = await sim_client.push_tx(generic_bundle)
assert result == (MempoolInclusionStatus.SUCCESS, None)
await sim.farm_block()
ownership_coin = (await sim_client.get_coin_records_by_puzzle_hash(ownership_ph, include_spent_coins=False))[
0
].coin
skip_tp_spend = CoinSpend(
ownership_coin,
ownership_puzzle,
Program.to([[[51, ACS_PH, 1]]]),
)
skip_tp_bundle = SpendBundle([skip_tp_spend], G2Element())
result = await sim_client.push_tx(skip_tp_bundle)
assert result == (MempoolInclusionStatus.FAILED, Err.GENERATOR_RUNTIME_ERROR)
with pytest.raises(ValueError, match="clvm raise"):
skip_tp_spend.puzzle_reveal.to_program().run(skip_tp_spend.solution.to_program())
make_bad_announcement_spend = CoinSpend(
ownership_coin,
ownership_puzzle,
Program.to(
[
[
[51, ACS_PH, 1],
[-10, TARGET_OWNER, TARGET_TP],
[62, b"\xad\x4c" + bytes32([0] * 32)],
]
]
),
)
make_bad_announcement_bundle = SpendBundle([make_bad_announcement_spend], G2Element())
result = await sim_client.push_tx(make_bad_announcement_bundle)
assert result == (MempoolInclusionStatus.FAILED, Err.GENERATOR_RUNTIME_ERROR)
with pytest.raises(ValueError, match="clvm raise"):
make_bad_announcement_spend.puzzle_reveal.to_program().run(
make_bad_announcement_spend.solution.to_program()
)
expected_announcement = Announcement(
ownership_puzzle.get_tree_hash(),
b"\xad\x4c" + Program.to([TARGET_OWNER, TARGET_TP]).get_tree_hash(),
)
harmless_announcement = Announcement(
ownership_puzzle.get_tree_hash(),
b"oy",
)
update_everything_spend = CoinSpend(
ownership_coin,
ownership_puzzle,
Program.to(
[
[
[51, ACS_PH, 1],
[-10, TARGET_OWNER, TARGET_TP],
[62, harmless_announcement.message], # create a harmless puzzle announcement
[63, expected_announcement.name()],
[63, harmless_announcement.name()],
]
]
),
)
update_everything_bundle = SpendBundle([update_everything_spend], G2Element())
result = await sim_client.push_tx(update_everything_bundle)
assert result == (MempoolInclusionStatus.SUCCESS, None)
await sim.farm_block()
assert (await sim_client.get_coin_records_by_parent_ids([ownership_coin.name()], include_spent_coins=False))[
0
].coin.puzzle_hash == construct_ownership_layer(
TARGET_OWNER,
TARGET_TP,
ACS,
).get_tree_hash()
finally:
await sim.close() # type: ignore
@pytest.mark.asyncio()
async def test_default_transfer_program(setup_sim: Tuple[SpendSim, SimClient]) -> None:
sim, sim_client = setup_sim
try:
# Now make the ownership coin
FAKE_SINGLETON_MOD = Program.to([2, 5, 11]) # (a 5 11) | (mod (_ INNER_PUZ inner_sol) (a INNER_PUZ inner_sol))
FAKE_CAT_MOD = Program.to([2, 11, 23]) # (a 11 23) or (mod (_ _ INNER_PUZ inner_sol) (a INNER_PUZ inner_sol))
FAKE_LAUNCHER_ID = bytes32([0] * 32)
FAKE_TAIL = bytes32([2] * 32)
FAKE_SINGLETON_STRUCT = Program.to((FAKE_SINGLETON_MOD.get_tree_hash(), (FAKE_LAUNCHER_ID, FAKE_LAUNCHER_ID)))
FAKE_SINGLETON = FAKE_SINGLETON_MOD.curry(FAKE_SINGLETON_STRUCT, ACS)
FAKE_CAT = FAKE_CAT_MOD.curry(FAKE_CAT_MOD.get_tree_hash(), FAKE_TAIL, ACS)
ROYALTY_ADDRESS = bytes32([1] * 32)
TRADE_PRICE_PERCENTAGE = 5000 # 50%
transfer_program: Program = NFT_TRANSFER_PROGRAM_DEFAULT.curry(
FAKE_SINGLETON_STRUCT,
ROYALTY_ADDRESS,
TRADE_PRICE_PERCENTAGE,
)
ownership_puzzle: Program = construct_ownership_layer(
None,
transfer_program,
ACS,
)
ownership_ph: bytes32 = ownership_puzzle.get_tree_hash()
await sim.farm_block(ownership_ph)
ownership_coin = (await sim_client.get_coin_records_by_puzzle_hash(ownership_ph, include_spent_coins=False))[
0
].coin
BLOCK_HEIGHT = sim.block_height
# Try a spend, no royalties, no owner update
generic_spend = CoinSpend(
ownership_coin,
ownership_puzzle,
Program.to([[[51, ACS_PH, 1]]]),
)
generic_bundle = SpendBundle([generic_spend], G2Element())
result = await sim_client.push_tx(generic_bundle)
assert result == (MempoolInclusionStatus.SUCCESS, None)
await sim.farm_block()
assert len(await sim_client.get_coin_records_by_puzzle_hash(ownership_ph, include_spent_coins=False)) > 0
await sim.rewind(BLOCK_HEIGHT)
# Now try an owner update plus royalties
await sim.farm_block(FAKE_SINGLETON.get_tree_hash())
await sim.farm_block(FAKE_CAT.get_tree_hash())
await sim.farm_block(ACS_PH)
singleton_coin = (
await sim_client.get_coin_records_by_puzzle_hash(FAKE_SINGLETON.get_tree_hash(), include_spent_coins=False)
)[0].coin
cat_coin = (
await sim_client.get_coin_records_by_puzzle_hash(FAKE_CAT.get_tree_hash(), include_spent_coins=False)
)[0].coin
xch_coin = (await sim_client.get_coin_records_by_puzzle_hash(ACS_PH, include_spent_coins=False))[0].coin
ownership_spend = CoinSpend(
ownership_coin,
ownership_puzzle,
Program.to(
[[[51, ACS_PH, 1], [-10, FAKE_LAUNCHER_ID, [[100, ACS_PH], [100, FAKE_CAT.get_tree_hash()]], ACS_PH]]]
),
)
did_announcement_spend = CoinSpend(
singleton_coin,
FAKE_SINGLETON,
Program.to([[[62, FAKE_LAUNCHER_ID]]]),
)
expected_announcement_data = Program.to(
(FAKE_LAUNCHER_ID, [[ROYALTY_ADDRESS, 50, [ROYALTY_ADDRESS]]])
).get_tree_hash()
xch_announcement_spend = CoinSpend(
xch_coin,
ACS,
Program.to([[62, expected_announcement_data]]),
)
cat_announcement_spend = CoinSpend(cat_coin, FAKE_CAT, Program.to([[[62, expected_announcement_data]]]))
# Make sure every combo except all of them work
for i in range(1, 3):
for announcement_combo in itertools.combinations(
[did_announcement_spend, xch_announcement_spend, cat_announcement_spend], i
):
result = await sim_client.push_tx(SpendBundle([ownership_spend, *announcement_combo], G2Element()))
assert result == (MempoolInclusionStatus.FAILED, Err.ASSERT_ANNOUNCE_CONSUMED_FAILED)
# Make sure all of them together pass
full_bundle = SpendBundle(
[ownership_spend, did_announcement_spend, xch_announcement_spend, cat_announcement_spend], G2Element()
)
result = await sim_client.push_tx(full_bundle)
assert result == (MempoolInclusionStatus.SUCCESS, None)
# Finally, make sure we can just clear the DID label off
new_ownership_puzzle: Program = construct_ownership_layer(
FAKE_LAUNCHER_ID,
transfer_program,
ACS,
)
new_ownership_ph: bytes32 = new_ownership_puzzle.get_tree_hash()
await sim.farm_block(new_ownership_ph)
new_ownership_coin = (
await sim_client.get_coin_records_by_puzzle_hash(new_ownership_ph, include_spent_coins=False)
)[0].coin
empty_spend = CoinSpend(
new_ownership_coin,
new_ownership_puzzle,
Program.to([[[51, ACS_PH, 1], [-10, [], [], []]]]),
)
empty_bundle = SpendBundle([empty_spend], G2Element())
result = await sim_client.push_tx(empty_bundle)
assert result == (MempoolInclusionStatus.SUCCESS, None)
await sim.farm_block()
finally:
await sim.close() # type: ignore
+846
View File
@@ -0,0 +1,846 @@
import asyncio
from secrets import token_bytes
from typing import Any, Dict, Optional
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.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, uint64
from chia.wallet.cat_wallet.cat_wallet import CATWallet
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.outer_puzzles import create_asset_id, match_puzzle
from chia.wallet.puzzle_drivers import PuzzleInfo
from chia.wallet.trading.offer import Offer
from chia.wallet.trading.trade_status import TradeStatus
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
async def get_trade_and_status(trade_manager, trade) -> TradeStatus: # type: ignore
trade_rec = await trade_manager.get_trade_by_id(trade.trade_id)
return TradeStatus(trade_rec.status)
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_offer_with_fee(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_maker = wallet_node_0.wallet_state_manager.main_wallet
wallet_taker = wallet_node_1.wallet_state_manager.main_wallet
maker_ph = await wallet_maker.get_new_puzzlehash()
taker_ph = await wallet_taker.get_new_puzzlehash()
token_ph = bytes32(token_bytes())
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(maker_ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(taker_ph))
await asyncio.sleep(5)
funds = sum(
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)]
)
await time_out_assert(15, wallet_maker.get_unconfirmed_balance, funds)
await time_out_assert(15, wallet_maker.get_confirmed_balance, funds)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
nft_wallet_maker = await NFTWallet.create_new_nft_wallet(
wallet_node_0.wallet_state_manager, wallet_maker, name="NFT WALLET 1"
)
nft_wallet_taker = await NFTWallet.create_new_nft_wallet(
wallet_node_1.wallet_state_manager, wallet_taker, name="NFT WALLET 2"
)
trade_manager_maker = wallet_maker.wallet_state_manager.trade_manager
trade_manager_taker = wallet_taker.wallet_state_manager.trade_manager
metadata = Program.to(
[
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F185"),
]
)
sb = await nft_wallet_maker.generate_new_nft(metadata)
assert sb
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(token_ph))
await asyncio.sleep(5)
coins_maker = nft_wallet_maker.my_nft_coins
assert len(coins_maker) == 1
coins_taker = nft_wallet_taker.my_nft_coins
assert len(coins_taker) == 0
# MAKE FIRST TRADE: 1 NFT for 100 xch
maker_balance_pre = await wallet_maker.get_confirmed_balance()
taker_balance_pre = await wallet_taker.get_confirmed_balance()
nft_to_offer = coins_maker[0]
nft_info: Optional[PuzzleInfo] = match_puzzle(nft_to_offer.full_puzzle)
nft_asset_id: bytes32 = create_asset_id(nft_info) # type: ignore
driver_dict: Dict[bytes32, Optional[PuzzleInfo]] = {nft_asset_id: nft_info}
xch_request = 100
maker_fee = uint64(10)
offer_nft_for_xch = {wallet_maker.id(): xch_request, nft_asset_id: -1}
success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
offer_nft_for_xch, driver_dict, fee=maker_fee
)
await asyncio.sleep(1)
assert success is True
assert error is None
assert trade_make is not None
taker_fee = uint64(1)
success, trade_take, error = await trade_manager_taker.respond_to_offer(
Offer.from_bytes(trade_make.offer), fee=taker_fee
)
await asyncio.sleep(1)
assert success
assert error is None
assert trade_take is not None
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take)
await time_out_assert(15, wallet_maker.get_confirmed_balance, maker_balance_pre + xch_request - maker_fee)
await time_out_assert(15, wallet_taker.get_confirmed_balance, taker_balance_pre - xch_request - taker_fee)
coins_maker = nft_wallet_maker.my_nft_coins
coins_taker = nft_wallet_taker.my_nft_coins
assert len(coins_maker) == 0
assert len(coins_taker) == 1
# MAKE SECOND TRADE: 100 xch for 1 NFT
maker_balance_pre = await wallet_maker.get_confirmed_balance()
taker_balance_pre = await wallet_taker.get_confirmed_balance()
nft_to_buy = coins_taker[0]
nft_to_buy_info: Optional[PuzzleInfo] = match_puzzle(nft_to_buy.full_puzzle)
nft_to_buy_asset_id: bytes32 = create_asset_id(nft_to_buy_info) # type: ignore
driver_dict_to_buy: Dict[bytes32, Optional[PuzzleInfo]] = {nft_to_buy_asset_id: nft_to_buy_info}
xch_offered = 1000
maker_fee = uint64(10)
offer_xch_for_nft = {wallet_maker.id(): -xch_offered, nft_to_buy_asset_id: 1}
success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
offer_xch_for_nft, driver_dict_to_buy, fee=maker_fee
)
await asyncio.sleep(1)
assert success is True
assert error is None
assert trade_make is not None
taker_fee = uint64(1)
success, trade_take, error = await trade_manager_taker.respond_to_offer(
Offer.from_bytes(trade_make.offer), fee=taker_fee
)
await asyncio.sleep(1)
assert success
assert error is None
assert trade_take is not None
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take)
await time_out_assert(15, wallet_maker.get_confirmed_balance, maker_balance_pre - xch_offered - maker_fee)
await time_out_assert(15, wallet_taker.get_confirmed_balance, taker_balance_pre + xch_offered - taker_fee)
coins_maker = nft_wallet_maker.my_nft_coins
coins_taker = nft_wallet_taker.my_nft_coins
assert len(coins_maker) == 1
assert len(coins_taker) == 0
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_offer_cancellations(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_maker = wallet_node_0.wallet_state_manager.main_wallet
wallet_taker = wallet_node_1.wallet_state_manager.main_wallet
maker_ph = await wallet_maker.get_new_puzzlehash()
taker_ph = await wallet_taker.get_new_puzzlehash()
token_ph = bytes32(token_bytes())
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(maker_ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(taker_ph))
await asyncio.sleep(5)
funds = sum(
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)]
)
await time_out_assert(15, wallet_maker.get_unconfirmed_balance, funds)
await time_out_assert(15, wallet_maker.get_confirmed_balance, funds)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
nft_wallet_maker = await NFTWallet.create_new_nft_wallet(
wallet_node_0.wallet_state_manager, wallet_maker, name="NFT WALLET 1"
)
nft_wallet_taker = await NFTWallet.create_new_nft_wallet(
wallet_node_1.wallet_state_manager, wallet_taker, name="NFT WALLET 2"
)
trade_manager_maker = wallet_maker.wallet_state_manager.trade_manager
# trade_manager_taker = wallet_taker.wallet_state_manager.trade_manager
metadata = Program.to(
[
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F185"),
]
)
sb = await nft_wallet_maker.generate_new_nft(metadata)
assert sb
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(token_ph))
await asyncio.sleep(5)
coins_maker = nft_wallet_maker.my_nft_coins
assert len(coins_maker) == 1
coins_taker = nft_wallet_taker.my_nft_coins
assert len(coins_taker) == 0
# maker creates offer and cancels
maker_balance_pre = await wallet_maker.get_confirmed_balance()
# taker_balance_pre = await wallet_taker.get_confirmed_balance()
nft_to_offer = coins_maker[0]
nft_info: Optional[PuzzleInfo] = match_puzzle(nft_to_offer.full_puzzle)
nft_asset_id: bytes32 = create_asset_id(nft_info) # type: ignore
driver_dict: Dict[bytes32, Optional[PuzzleInfo]] = {nft_asset_id: nft_info}
xch_request = 100
maker_fee = uint64(10)
offer_nft_for_xch = {wallet_maker.id(): xch_request, nft_asset_id: -1}
success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
offer_nft_for_xch, driver_dict, fee=maker_fee
)
await asyncio.sleep(1)
assert success is True
assert error is None
assert trade_make is not None
# await trade_manager_maker.cancel_pending_offer(trade_make.trade_id)
# await time_out_assert(15, get_trade_and_status, TradeStatus.CANCELLED, trade_manager_maker, trade_make)
cancel_fee = uint64(10)
txs = await trade_manager_maker.cancel_pending_offer_safely(trade_make.trade_id, fee=cancel_fee)
await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make)
for tx in txs:
if tx.spend_bundle is not None:
await time_out_assert(15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx.spend_bundle.name())
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
await time_out_assert(15, get_trade_and_status, TradeStatus.CANCELLED, trade_manager_maker, trade_make)
maker_balance = await wallet_maker.get_confirmed_balance()
assert maker_balance == maker_balance_pre - cancel_fee
coins_maker = nft_wallet_maker.my_nft_coins
assert len(coins_maker) == 1
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_offer_with_metadata_update(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_maker = wallet_node_0.wallet_state_manager.main_wallet
wallet_taker = wallet_node_1.wallet_state_manager.main_wallet
maker_ph = await wallet_maker.get_new_puzzlehash()
taker_ph = await wallet_taker.get_new_puzzlehash()
token_ph = bytes32(token_bytes())
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(maker_ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(taker_ph))
await asyncio.sleep(5)
funds = sum(
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)]
)
await time_out_assert(15, wallet_maker.get_unconfirmed_balance, funds)
await time_out_assert(15, wallet_maker.get_confirmed_balance, funds)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
nft_wallet_maker = await NFTWallet.create_new_nft_wallet(
wallet_node_0.wallet_state_manager, wallet_maker, name="NFT WALLET 1"
)
nft_wallet_taker = await NFTWallet.create_new_nft_wallet(
wallet_node_1.wallet_state_manager, wallet_taker, name="NFT WALLET 2"
)
trade_manager_maker = wallet_maker.wallet_state_manager.trade_manager
trade_manager_taker = wallet_taker.wallet_state_manager.trade_manager
metadata = Program.to(
[
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F185"),
("mu", []),
("lu", []),
("sn", uint64(1)),
("st", uint64(1)),
]
)
sb = await nft_wallet_maker.generate_new_nft(metadata)
assert sb
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(token_ph))
await asyncio.sleep(5)
coins_maker = nft_wallet_maker.my_nft_coins
assert len(coins_maker) == 1
coins_taker = nft_wallet_taker.my_nft_coins
assert len(coins_taker) == 0
# Maker updates metadata:
nft_to_update = coins_maker[0]
url_to_add = "https://new_url.com"
key = "mu"
fee_for_update = uint64(10)
update_sb = await nft_wallet_maker.update_metadata(nft_to_update, key, url_to_add, fee=fee_for_update)
mempool_mgr = full_node_api.full_node.mempool_manager
await time_out_assert_not_none(5, mempool_mgr.get_spendbundle, update_sb.name()) # type: ignore
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
coins_maker = nft_wallet_maker.my_nft_coins
updated_nft = coins_maker[0]
updated_nft_info = match_puzzle(updated_nft.full_puzzle)
assert url_to_add in updated_nft_info.also().info["metadata"] # type: ignore
# MAKE FIRST TRADE: 1 NFT for 100 xch
maker_balance_pre = await wallet_maker.get_confirmed_balance()
taker_balance_pre = await wallet_taker.get_confirmed_balance()
nft_to_offer = coins_maker[0]
nft_info: Optional[PuzzleInfo] = match_puzzle(nft_to_offer.full_puzzle)
nft_asset_id: bytes32 = create_asset_id(nft_info) # type: ignore
driver_dict: Dict[bytes32, Optional[PuzzleInfo]] = {nft_asset_id: nft_info}
xch_request = 100
maker_fee = uint64(10)
offer_nft_for_xch = {wallet_maker.id(): xch_request, nft_asset_id: -1}
success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
offer_nft_for_xch, driver_dict, fee=maker_fee
)
await asyncio.sleep(1)
assert success is True
assert error is None
assert trade_make is not None
taker_fee = uint64(1)
success, trade_take, error = await trade_manager_taker.respond_to_offer(
Offer.from_bytes(trade_make.offer), fee=taker_fee
)
await asyncio.sleep(1)
assert success
assert error is None
assert trade_take is not None
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take)
await time_out_assert(15, wallet_maker.get_confirmed_balance, maker_balance_pre + xch_request - maker_fee)
await time_out_assert(15, wallet_taker.get_confirmed_balance, taker_balance_pre - xch_request - taker_fee)
coins_maker = nft_wallet_maker.my_nft_coins
coins_taker = nft_wallet_taker.my_nft_coins
assert len(coins_maker) == 0
assert len(coins_taker) == 1
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_offer_nft_for_cat(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_maker = wallet_node_0.wallet_state_manager.main_wallet
wallet_taker = wallet_node_1.wallet_state_manager.main_wallet
maker_ph = await wallet_maker.get_new_puzzlehash()
taker_ph = await wallet_taker.get_new_puzzlehash()
token_ph = bytes32(token_bytes())
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(maker_ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(taker_ph))
await asyncio.sleep(5)
funds = sum(
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)]
)
await time_out_assert(15, wallet_maker.get_unconfirmed_balance, funds)
await time_out_assert(15, wallet_maker.get_confirmed_balance, funds)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
# Create NFT wallets and nfts for maker and taker
nft_wallet_maker = await NFTWallet.create_new_nft_wallet(
wallet_node_0.wallet_state_manager, wallet_maker, name="NFT WALLET 1"
)
nft_wallet_taker = await NFTWallet.create_new_nft_wallet(
wallet_node_1.wallet_state_manager, wallet_taker, name="NFT WALLET 2"
)
trade_manager_maker = wallet_maker.wallet_state_manager.trade_manager
trade_manager_taker = wallet_taker.wallet_state_manager.trade_manager
metadata = Program.to(
[
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F185"),
]
)
sb = await nft_wallet_maker.generate_new_nft(metadata)
assert sb
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(token_ph))
await asyncio.sleep(5)
coins_maker = nft_wallet_maker.my_nft_coins
assert len(coins_maker) == 1
coins_taker = nft_wallet_taker.my_nft_coins
assert len(coins_taker) == 0
# Create two new CATs and wallets for maker and taker
cats_to_mint = 10000
async with wallet_node_0.wallet_state_manager.lock:
cat_wallet_maker: CATWallet = await CATWallet.create_new_cat_wallet(
wallet_node_0.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, uint64(cats_to_mint)
)
await asyncio.sleep(1)
async with wallet_node_1.wallet_state_manager.lock:
cat_wallet_taker: CATWallet = await CATWallet.create_new_cat_wallet(
wallet_node_1.wallet_state_manager, wallet_taker, {"identifier": "genesis_by_id"}, uint64(cats_to_mint)
)
await asyncio.sleep(1)
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
await time_out_assert(15, cat_wallet_maker.get_confirmed_balance, cats_to_mint)
await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance, cats_to_mint)
await time_out_assert(15, cat_wallet_taker.get_confirmed_balance, cats_to_mint)
await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance, cats_to_mint)
wallet_maker_for_taker_cat: CATWallet = await CATWallet.create_wallet_for_cat(
wallet_node_0.wallet_state_manager, wallet_maker, cat_wallet_taker.get_asset_id()
)
wallet_taker_for_maker_cat: CATWallet = await CATWallet.create_wallet_for_cat(
wallet_node_1.wallet_state_manager, wallet_taker, cat_wallet_maker.get_asset_id()
)
assert wallet_taker_for_maker_cat
# MAKE FIRST TRADE: 1 NFT for 10 taker cats
maker_balance_pre = await wallet_maker.get_confirmed_balance()
taker_balance_pre = await wallet_taker.get_confirmed_balance()
taker_cat_maker_balance_pre = await wallet_maker_for_taker_cat.get_confirmed_balance()
taker_cat_taker_balance_pre = await cat_wallet_taker.get_confirmed_balance()
nft_to_offer = coins_maker[0]
nft_info: Optional[PuzzleInfo] = match_puzzle(nft_to_offer.full_puzzle)
nft_asset_id: bytes32 = create_asset_id(nft_info) # type: ignore
driver_dict: Dict[bytes32, Optional[PuzzleInfo]] = {nft_asset_id: nft_info}
maker_fee = uint64(10)
taker_cat_offered = 2500
offer_nft_for_cat = {nft_asset_id: -1, wallet_maker_for_taker_cat.id(): taker_cat_offered}
success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
offer_nft_for_cat, driver_dict, fee=maker_fee
)
await asyncio.sleep(1)
assert success is True
assert error is None
assert trade_make is not None
taker_fee = uint64(1)
success, trade_take, error = await trade_manager_taker.respond_to_offer(
Offer.from_bytes(trade_make.offer), fee=taker_fee
)
await asyncio.sleep(1)
assert success
assert error is None
assert trade_take is not None
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take)
taker_cat_maker_balance_post = await wallet_maker_for_taker_cat.get_confirmed_balance()
taker_cat_taker_balance_post = await cat_wallet_taker.get_confirmed_balance()
assert taker_cat_maker_balance_post == taker_cat_maker_balance_pre + taker_cat_offered
assert taker_cat_taker_balance_post == taker_cat_taker_balance_pre - taker_cat_offered
maker_balance_post = await wallet_maker.get_confirmed_balance()
taker_balance_post = await wallet_taker.get_confirmed_balance()
assert maker_balance_post == maker_balance_pre - maker_fee
assert taker_balance_post == taker_balance_pre - taker_fee
coins_maker = nft_wallet_maker.my_nft_coins
coins_taker = nft_wallet_taker.my_nft_coins
assert len(coins_maker) == 0
assert len(coins_taker) == 1
# Make an offer for taker NFT for multiple cats
maker_cat_amount = 400
taker_cat_amount = 500
nft_to_buy = coins_taker[0]
nft_to_buy_info: Optional[PuzzleInfo] = match_puzzle(nft_to_buy.full_puzzle)
nft_to_buy_asset_id: bytes32 = create_asset_id(nft_to_buy_info) # type: ignore
driver_dict_to_buy: Dict[bytes32, Optional[PuzzleInfo]] = {
nft_to_buy_asset_id: nft_to_buy_info,
}
maker_fee = uint64(10)
offer_multi_cats_for_nft = {
nft_to_buy_asset_id: 1,
wallet_maker_for_taker_cat.id(): -taker_cat_amount,
cat_wallet_maker.id(): -maker_cat_amount,
}
success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
offer_multi_cats_for_nft, driver_dict_to_buy, fee=maker_fee
)
await asyncio.sleep(1)
assert success is True
assert error is None
assert trade_make is not None
taker_fee = uint64(1)
success, trade_take, error = await trade_manager_taker.respond_to_offer(
Offer.from_bytes(trade_make.offer), fee=taker_fee
)
await asyncio.sleep(1)
assert success
assert error is None
assert trade_take is not None
# check balances: taker wallet down an NFT, up cats
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take)
taker_cat_maker_balance_post_2 = await wallet_maker_for_taker_cat.get_confirmed_balance()
taker_cat_taker_balance_post_2 = await cat_wallet_taker.get_confirmed_balance()
assert taker_cat_maker_balance_post_2 == taker_cat_maker_balance_post - taker_cat_amount
assert taker_cat_taker_balance_post_2 == taker_cat_taker_balance_post + taker_cat_amount
maker_balance_post_2 = await wallet_maker.get_confirmed_balance()
taker_balance_post_2 = await wallet_taker.get_confirmed_balance()
assert maker_balance_post_2 == maker_balance_post - maker_fee
assert taker_balance_post_2 == taker_balance_post - taker_fee
coins_maker = nft_wallet_maker.my_nft_coins
coins_taker = nft_wallet_taker.my_nft_coins
assert len(coins_maker) == 1
assert len(coins_taker) == 0
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_offer_nft_for_nft(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_maker = wallet_node_0.wallet_state_manager.main_wallet
wallet_taker = wallet_node_1.wallet_state_manager.main_wallet
maker_ph = await wallet_maker.get_new_puzzlehash()
taker_ph = await wallet_taker.get_new_puzzlehash()
token_ph = bytes32(token_bytes())
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(maker_ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(taker_ph))
await asyncio.sleep(5)
funds = sum(
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)]
)
await time_out_assert(15, wallet_maker.get_unconfirmed_balance, funds)
await time_out_assert(15, wallet_maker.get_confirmed_balance, funds)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
# Create NFT wallets and nfts for maker and taker
nft_wallet_maker = await NFTWallet.create_new_nft_wallet(
wallet_node_0.wallet_state_manager, wallet_maker, name="NFT WALLET 1"
)
nft_wallet_taker = await NFTWallet.create_new_nft_wallet(
wallet_node_1.wallet_state_manager, wallet_taker, name="NFT WALLET 2"
)
trade_manager_maker = wallet_maker.wallet_state_manager.trade_manager
trade_manager_taker = wallet_taker.wallet_state_manager.trade_manager
metadata = Program.to(
[
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F185"),
]
)
sb = await nft_wallet_maker.generate_new_nft(metadata)
assert sb
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
metadata_2 = Program.to(
[
("u", ["https://www.chia.net/image2.html"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F183"),
]
)
sb_2 = await nft_wallet_taker.generate_new_nft(metadata_2)
assert sb_2
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb_2.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
coins_maker = nft_wallet_maker.my_nft_coins
assert len(coins_maker) == 1
coins_taker = nft_wallet_taker.my_nft_coins
assert len(coins_taker) == 1
maker_balance_pre = await wallet_maker.get_confirmed_balance()
taker_balance_pre = await wallet_taker.get_confirmed_balance()
nft_to_offer = coins_maker[0]
nft_to_offer_info: Optional[PuzzleInfo] = match_puzzle(nft_to_offer.full_puzzle)
nft_to_offer_asset_id: bytes32 = create_asset_id(nft_to_offer_info) # type: ignore
nft_to_take = coins_taker[0]
nft_to_take_info: Optional[PuzzleInfo] = match_puzzle(nft_to_take.full_puzzle)
nft_to_take_asset_id: bytes32 = create_asset_id(nft_to_take_info) # type: ignore
driver_dict: Dict[bytes32, Optional[PuzzleInfo]] = {
nft_to_offer_asset_id: nft_to_offer_info,
nft_to_take_asset_id: nft_to_take_info,
}
maker_fee = uint64(10)
offer_nft_for_nft = {nft_to_take_asset_id: 1, nft_to_offer_asset_id: -1}
success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
offer_nft_for_nft, driver_dict, fee=maker_fee
)
await asyncio.sleep(1)
assert success is True
assert error is None
assert trade_make is not None
taker_fee = uint64(1)
success, trade_take, error = await trade_manager_taker.respond_to_offer(
Offer.from_bytes(trade_make.offer), fee=taker_fee
)
await asyncio.sleep(1)
assert success
assert error is None
assert trade_take is not None
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph))
await asyncio.sleep(5)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make)
await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take)
await time_out_assert(15, wallet_maker.get_confirmed_balance, maker_balance_pre - maker_fee)
await time_out_assert(15, wallet_taker.get_confirmed_balance, taker_balance_pre - taker_fee)
coins_maker = nft_wallet_maker.my_nft_coins
coins_taker = nft_wallet_taker.my_nft_coins
assert len(coins_maker) == 1
assert len(coins_taker) == 1
+193
View File
@@ -0,0 +1,193 @@
from secrets import token_bytes
from typing import Tuple
from clvm.casts import int_from_bytes
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.wallet.nft_wallet import uncurry_nft
from chia.wallet.nft_wallet.nft_puzzles import (
construct_ownership_layer,
create_full_puzzle,
create_nft_layer_puzzle_with_curry_params,
recurry_nft_puzzle,
)
from chia.wallet.outer_puzzles import match_puzzle
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 tests.core.make_block_generator import int_to_public_key
SINGLETON_MOD = load_clvm("singleton_top_layer_v1_1.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")
NFT_OWNERSHIP_LAYER = load_clvm("nft_ownership_layer.clvm")
NFT_TRANSFER_PROGRAM_DEFAULT = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties.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()
OFFER_MOD = load_clvm("settlement_payments.clvm")
LAUNCHER_ID = Program.to(b"launcher-id").get_tree_hash()
NFT_METADATA_UPDATER_DEFAULT = load_clvm("nft_metadata_updater_default.clvm")
def test_nft_transfer_puzzle_hashes():
maker_pk = int_to_public_key(111)
maker_p2_puz = puzzle_for_pk(maker_pk)
maker_p2_ph = maker_p2_puz.get_tree_hash()
maker_did = Program.to("maker did").get_tree_hash()
# maker_did_inner_hash = Program.to("maker did inner hash").get_tree_hash()
metadata = [
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
]
metadata_updater_hash = NFT_METADATA_UPDATER_DEFAULT.get_tree_hash()
# royalty_addr = maker_p2_ph
royalty_pc = 2000 # basis pts
nft_id = Program.to("nft id").get_tree_hash()
SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (nft_id, LAUNCHER_PUZZLE_HASH)))
transfer_puz = NFT_TRANSFER_PROGRAM_DEFAULT.curry(
SINGLETON_STRUCT,
maker_p2_ph,
royalty_pc,
)
ownership_puz = NFT_OWNERSHIP_LAYER.curry(
NFT_OWNERSHIP_LAYER.get_tree_hash(), maker_did, transfer_puz, maker_p2_puz
)
metadata_puz = NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD.get_tree_hash(), metadata, metadata_updater_hash, ownership_puz
)
nft_puz = SINGLETON_MOD.curry(SINGLETON_STRUCT, metadata_puz)
nft_info = match_puzzle(nft_puz)
assert nft_info.also().also() is not None
unft = uncurry_nft.UncurriedNFT.uncurry(nft_puz)
assert unft.supports_did
# setup transfer
taker_pk = int_to_public_key(222)
taker_p2_puz = puzzle_for_pk(taker_pk)
taker_p2_ph = taker_p2_puz.get_tree_hash()
# make nft solution
fake_lineage_proof = Program.to([token_bytes(32), maker_p2_ph, 1])
transfer_conditions = Program.to([[51, taker_p2_ph, 1, [taker_p2_ph]], [-10, [], [], []]])
ownership_sol = Program.to([solution_for_conditions(transfer_conditions)])
metadata_sol = Program.to([ownership_sol])
nft_sol = Program.to([fake_lineage_proof, 1, metadata_sol])
conds = nft_puz.run(nft_sol)
# get the new NFT puzhash
for cond in conds.as_iter():
if cond.first().as_int() == 51:
expected_ph = bytes32(cond.at("rf").atom)
# recreate the puzzle for new_puzhash
new_ownership_puz = NFT_OWNERSHIP_LAYER.curry(NFT_OWNERSHIP_LAYER.get_tree_hash(), None, transfer_puz, taker_p2_puz)
new_metadata_puz = NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD.get_tree_hash(), metadata, metadata_updater_hash, new_ownership_puz
)
new_nft_puz = SINGLETON_MOD.curry(SINGLETON_STRUCT, new_metadata_puz)
calculated_ph = new_nft_puz.get_tree_hash()
assert expected_ph == calculated_ph
def make_a_new_solution() -> Tuple[Program, Program]:
destination = int_to_public_key(2)
p2_puzzle = puzzle_for_pk(destination)
puzhash = p2_puzzle.get_tree_hash()
new_did = Program.to("test").get_tree_hash()
new_did_inner_hash = Program.to("fake").get_tree_hash()
trade_prices_list = [[200, OFFER_MOD.get_tree_hash()]]
condition_list = [
[
51,
puzhash,
1,
[puzhash],
],
[-10, new_did, trade_prices_list, new_did_inner_hash],
]
solution = Program.to(
[
[],
[],
[
[solution_for_conditions(condition_list)],
],
],
)
return p2_puzzle, solution
def make_a_new_ownership_layer_puzzle() -> Tuple[Program, Program]:
pubkey = int_to_public_key(1)
innerpuz = puzzle_for_pk(pubkey)
old_did = Program.to("test_2").get_tree_hash()
nft_id = Program.to("nft_id")
SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (nft_id, LAUNCHER_PUZZLE_HASH)))
curried_tp = NFT_TRANSFER_PROGRAM_DEFAULT.curry(
SINGLETON_STRUCT,
innerpuz.get_tree_hash(),
2000,
)
curried_inner = innerpuz
curried_ownership_layer = construct_ownership_layer(old_did, curried_tp, curried_inner)
return innerpuz, curried_ownership_layer
def make_a_new_nft_puzzle(curried_ownership_layer: Program, metadata: Program) -> Program:
curried_state_layer = NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(), curried_ownership_layer
)
return curried_state_layer
def get_updated_nft_puzzle(puzzle: Program, solution: Program) -> bytes32:
result = puzzle.run(solution)
for condition in result.as_iter():
code = int_from_bytes(condition.first().atom)
if code == 51:
if int_from_bytes(condition.rest().rest().first().atom) == 1:
# this is our new puzzle hash
return bytes32(condition.rest().first().atom)
raise ValueError("No create coin condition found")
def test_transfer_puzzle_builder() -> None:
metadata = [
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
]
sp2_puzzle, solution = make_a_new_solution()
p2_puzzle, ownership_puzzle = make_a_new_ownership_layer_puzzle()
clvm_nft_puzzle = create_nft_layer_puzzle_with_curry_params(
Program.to(metadata), NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(), ownership_puzzle
)
puzzle = create_full_puzzle(
Program.to(["singleton_id"]).get_tree_hash(),
Program.to(metadata),
NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(),
ownership_puzzle,
)
clvm_puzzle_hash = get_updated_nft_puzzle(clvm_nft_puzzle, solution.at("rrf"))
unft = uncurry_nft.UncurriedNFT.uncurry(puzzle)
assert unft.nft_state_layer == clvm_nft_puzzle
assert unft.inner_puzzle == ownership_puzzle
assert unft.p2_puzzle == p2_puzzle
ol_puzzle = recurry_nft_puzzle(unft, solution, sp2_puzzle)
nft_puzzle = create_nft_layer_puzzle_with_curry_params(
Program.to(metadata), NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(), ol_puzzle
)
assert clvm_puzzle_hash == nft_puzzle.get_tree_hash()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,75 @@
from typing import Optional
from clvm_tools.binutils import assemble
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint16
from chia.wallet.nft_wallet.ownership_outer_puzzle import puzzle_for_ownership_layer
from chia.wallet.nft_wallet.transfer_program_puzzle import puzzle_for_transfer_program
from chia.wallet.outer_puzzles import (
construct_puzzle,
create_asset_id,
get_inner_puzzle,
get_inner_solution,
match_puzzle,
solve_puzzle,
)
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
def test_ownership_outer_puzzle() -> None:
ACS = Program.to(1)
NIL = Program.to([])
owner = bytes32([0] * 32)
# (mod (current_owner conditions solution)
# (list current_owner () conditions)
# )
transfer_program = assemble( # type: ignore
"""
(c 2 (c () (c 5 ())))
"""
)
transfer_program_default: Program = puzzle_for_transfer_program(bytes32([1] * 32), bytes32([2] * 32), uint16(5000))
ownership_puzzle: Program = puzzle_for_ownership_layer(owner, transfer_program, ACS)
ownership_puzzle_empty: Program = puzzle_for_ownership_layer(NIL, transfer_program, ACS)
ownership_puzzle_default: Program = puzzle_for_ownership_layer(owner, transfer_program_default, ACS)
ownership_driver: Optional[PuzzleInfo] = match_puzzle(ownership_puzzle)
ownership_driver_empty: Optional[PuzzleInfo] = match_puzzle(ownership_puzzle_empty)
ownership_driver_default: Optional[PuzzleInfo] = match_puzzle(ownership_puzzle_default)
transfer_program_driver: Optional[PuzzleInfo] = match_puzzle(transfer_program_default)
assert ownership_driver is not None
assert ownership_driver_empty is not None
assert ownership_driver_default is not None
assert transfer_program_driver is not None
assert ownership_driver.type() == "ownership"
assert ownership_driver["owner"] == owner
assert ownership_driver_empty["owner"] == NIL
assert ownership_driver["transfer_program"] == transfer_program
assert ownership_driver_default["transfer_program"] == transfer_program_driver
assert transfer_program_driver.type() == "royalty transfer program"
assert transfer_program_driver["launcher_id"] == bytes32([1] * 32)
assert transfer_program_driver["royalty_address"] == bytes32([2] * 32)
assert transfer_program_driver["royalty_percentage"] == 5000
assert construct_puzzle(ownership_driver, ACS) == ownership_puzzle
assert construct_puzzle(ownership_driver_empty, ACS) == ownership_puzzle_empty
assert construct_puzzle(ownership_driver_default, ACS) == ownership_puzzle_default
assert get_inner_puzzle(ownership_driver, ownership_puzzle) == ACS
assert create_asset_id(ownership_driver) is None
# Set up for solve
inner_solution = Program.to(
[
[51, ACS.get_tree_hash(), 1],
[-10],
]
)
solution: Program = solve_puzzle(
ownership_driver,
Solver({}),
ACS,
inner_solution,
)
ownership_puzzle.run(solution)
assert get_inner_solution(ownership_driver, solution) == inner_solution
+72 -3
View File
@@ -33,6 +33,7 @@ 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.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.trading.trade_status import TradeStatus
from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.transaction_sorting import SortKey
@@ -734,6 +735,8 @@ async def test_offer_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment)
@pytest.mark.asyncio
async def test_did_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
from chia.wallet.did_wallet.did_info import DID_HRP
env: WalletRpcTestEnvironment = wallet_rpc_environment
wallet_1: Wallet = env.wallet_1.wallet
@@ -742,6 +745,7 @@ async def test_did_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
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
wallet_1_id = wallet_1.id()
await generate_funds(env.full_node.api, env.wallet_1, 5)
@@ -764,7 +768,7 @@ async def test_did_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
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)
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)
@@ -788,6 +792,8 @@ async def test_did_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
await farm_transaction_block(full_node_api, wallet_1_node)
# Update metadata
with pytest.raises(ValueError, match="Wallet with id 1 is not a DID one"):
await wallet_1_rpc.update_did_metadata(wallet_1_id, {"Twitter": "Https://test"})
res = await wallet_1_rpc.update_did_metadata(did_wallet_id_0, {"Twitter": "Https://test"})
assert res["success"]
@@ -812,16 +818,79 @@ async def test_did_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
did_wallets = list(
filter(
lambda w: (w.type == WalletType.DISTRIBUTED_ID),
lambda w: (w.type == WalletType.DECENTRALIZED_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
assert encode_puzzle_hash(bytes32.from_hexstr(did_wallet_2.get_my_DID()), DID_HRP) == did_id_0
metadata = json.loads(did_wallet_2.did_info.metadata)
assert metadata["Twitter"] == "Https://test"
@pytest.mark.asyncio
async def test_nft_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
from chia.wallet.nft_wallet.nft_info import NFT_HRP
env: WalletRpcTestEnvironment = wallet_rpc_environment
wallet_1_node: WalletNode = env.wallet_1.node
wallet_1_rpc: WalletRpcClient = env.wallet_1.rpc_client
wallet_2: Wallet = env.wallet_2.wallet
wallet_2_node: WalletNode = env.wallet_2.node
wallet_2_rpc: WalletRpcClient = env.wallet_2.rpc_client
full_node_api: FullNodeSimulator = env.full_node.api
await generate_funds(env.full_node.api, env.wallet_1, 5)
res = await wallet_1_rpc.create_new_nft_wallet(None)
nft_wallet_id = res["wallet_id"]
res = await wallet_1_rpc.mint_nft(
nft_wallet_id,
None,
None,
"0xD4584AD463139FA8C0D9F68F4B59F185",
["https://www.chia.net/img/branding/chia-logo.svg"],
)
assert res["success"]
for _ in range(3):
await farm_transaction_block(full_node_api, wallet_1_node)
assert wallet_1_node.wallet_state_manager is not None
nft_wallet: NFTWallet = wallet_1_node.wallet_state_manager.wallets[nft_wallet_id]
# Test with the hex version of nft_id
nft_id = nft_wallet.get_current_nfts()[0].coin.name().hex()
nft_info = (await wallet_1_rpc.get_nft_info(nft_id))["nft_info"]
assert nft_info["nft_coin_id"][2:] == nft_wallet.get_current_nfts()[0].coin.name().hex()
# Test with the bech32m version of nft_id
hmr_nft_id = encode_puzzle_hash(nft_wallet.get_current_nfts()[0].coin.name(), NFT_HRP)
nft_info = (await wallet_1_rpc.get_nft_info(hmr_nft_id))["nft_info"]
assert nft_info["nft_coin_id"][2:] == nft_wallet.get_current_nfts()[0].coin.name().hex()
addr = encode_puzzle_hash(await wallet_2.get_new_puzzlehash(), "txch")
res = await wallet_1_rpc.transfer_nft(nft_wallet_id, nft_id, addr, 0)
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
nft_wallet_id_1 = (
await wallet_2_node.wallet_state_manager.get_all_wallet_info_entries(wallet_type=WalletType.NFT)
)[0].id
nft_wallet_1: NFTWallet = wallet_2_node.wallet_state_manager.wallets[nft_wallet_id_1]
nft_info_1 = (await wallet_1_rpc.get_nft_info(nft_id, False))["nft_info"]
assert nft_info_1 == nft_info
nft_info_1 = (await wallet_1_rpc.get_nft_info(nft_id))["nft_info"]
assert nft_info_1["nft_coin_id"][2:] == nft_wallet_1.get_current_nfts()[0].coin.name().hex()
# Cross-check NFT
nft_info_2 = (await wallet_2_rpc.list_nfts(nft_wallet_id_1))["nft_list"][0]
assert nft_info_1 == nft_info_2
@pytest.mark.asyncio
async def test_key_and_address_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
env: WalletRpcTestEnvironment = wallet_rpc_environment
+7 -2
View File
@@ -6,7 +6,7 @@ from chia.protocols import full_node_protocol
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
from chia.types.peer_info import PeerInfo
from chia.util.ints import uint16, uint32
from tests.connection_utils import disconnect_all_and_reconnect
from tests.connection_utils import disconnect_all, disconnect_all_and_reconnect
from tests.pools.test_pool_rpc import wallet_is_synced
from tests.setup_nodes import test_constants
from tests.time_out_assert import time_out_assert
@@ -46,7 +46,9 @@ class TestWalletSync:
# Tests a reorg with the wallet
num_blocks = 30
blocks_reorg = bt.get_consecutive_blocks(num_blocks, block_list_input=default_400_blocks[:-5])
blocks_reorg = bt.get_consecutive_blocks(
num_blocks, block_list_input=default_400_blocks[:-5], current_time=True
)
for i in range(1, len(blocks_reorg)):
await full_node_api.full_node.respond_block(full_node_protocol.RespondBlock(blocks_reorg[i]))
@@ -57,6 +59,9 @@ class TestWalletSync:
await time_out_assert(
100, wallet_height_at_least, True, wallet_node, len(default_400_blocks) + num_blocks - 5 - 1
)
await time_out_assert(20, wallet_node.wallet_state_manager.synced)
await disconnect_all(wallet_server)
assert not (await wallet_node.wallet_state_manager.synced())
@pytest.mark.asyncio
async def test_almost_recent(self, bt, two_wallet_nodes, default_400_blocks, self_hostname):
+58
View File
@@ -0,0 +1,58 @@
from pathlib import Path
import aiosqlite
import pytest
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.db_wrapper import DBWrapper
from chia.util.ints import uint32, uint64
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.nft_wallet.nft_info import NFTCoinInfo
from chia.wallet.wallet_nft_store import WalletNftStore
class TestNftStore:
@pytest.mark.asyncio
async def test_nft_store(self) -> None:
db_filename = Path("nft_store_test.db")
if db_filename.exists():
db_filename.unlink()
con = await aiosqlite.connect(db_filename)
wrapper = DBWrapper(con)
db = await WalletNftStore.create(wrapper)
try:
a_bytes32 = bytes32.fromhex("09287c75377c63fd6a3a4d6658abed03e9a521e0436b1f83cdf4af99341ce8f1")
puzzle = Program.to(["A Test puzzle"])
nft = NFTCoinInfo(
a_bytes32,
Coin(a_bytes32, a_bytes32, uint64(1)),
LineageProof(a_bytes32, a_bytes32, uint64(1)),
puzzle,
uint32(10),
)
# Test save
await db.save_nft(uint32(1), a_bytes32, nft)
# Test get nft
assert nft == (await db.get_nft_list(wallet_id=uint32(1)))[0]
assert nft == (await db.get_nft_list())[0]
assert nft == (await db.get_nft_list(did_id=a_bytes32))[0]
assert nft == (await db.get_nft_list(wallet_id=uint32(1), did_id=a_bytes32))[0]
assert nft == await db.get_nft_by_id(a_bytes32)
# Test delete
await db.delete_nft(a_bytes32)
assert await db.get_nft_by_id(a_bytes32) is None
except Exception as e:
print(e, type(e))
await db._clear_database()
await db.close()
db_filename.unlink()
raise e
await db._clear_database()
await db.close()
db_filename.unlink()