Merge commit 'd0de8038cd95b71fa050f79e3685c51dcf05e13e' into catchup/long_lived_atari_from_release_1.4.0_d0de8038cd95b71fa050f79e3685c51dcf05e13e

This commit is contained in:
Kyle Altendorf 2022-06-13 17:44:12 -04:00
commit c429933737
No known key found for this signature in database
GPG Key ID: 5715D880FF005192
68 changed files with 4490 additions and 728 deletions

View File

@ -24,7 +24,7 @@ jobs:
build:
name: MacOS wallet-did_wallet Tests
runs-on: ${{ matrix.os }}
timeout-minutes: 30
timeout-minutes: 50
strategy:
fail-fast: false
max-parallel: 4
@ -67,7 +67,21 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
# Omitted checking out blocks and plots repo Chia-Network/test-cache
- name: Cache test blocks and plots
uses: actions/cache@v2
id: test-blocks-plots
with:
path: |
${{ github.workspace }}/.chia/blocks
${{ github.workspace }}/.chia/test-plots
key: 0.29.0
- name: Checkout test blocks and plots
if: steps.test-blocks-plots.outputs.cache-hit != 'true'
run: |
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf -
mkdir ${{ github.workspace }}/.chia
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
- name: Run install script
env:
@ -81,7 +95,7 @@ jobs:
- name: Test wallet-did_wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/did_wallet/test_did.py tests/wallet/did_wallet/test_did_rpc.py
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/did_wallet/test_did.py
- name: Process coverage data
run: |

View File

@ -0,0 +1,117 @@
#
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
#
name: MacOS wallet-nft_wallet Test
on:
push:
branches:
- 'long_lived/**'
- main
- 'release/**'
tags:
- '**'
pull_request:
branches:
- '**'
concurrency:
# SHA is added to the end if on `main` to let all main workflows run
group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/long_lived/')) && github.sha || '' }}
cancel-in-progress: true
jobs:
build:
name: MacOS wallet-nft_wallet Tests
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
max-parallel: 4
matrix:
python-version: ['3.9', '3.10']
os: [macOS-latest]
env:
CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet
JOB_FILE_NAME: tests_${{ matrix.os }}_python-${{ matrix.python-version }}_wallet-nft_wallet
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Python environment
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Create keychain for CI use
run: |
security create-keychain -p foo chiachain
security default-keychain -s chiachain
security unlock-keychain -p foo chiachain
security set-keychain-settings -t 7200 -u chiachain
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache pip
uses: actions/cache@v3
with:
# Note that new runners may break this https://github.com/actions/cache/issues/292
path: ${{ steps.pip-cache.outputs.dir }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Cache test blocks and plots
uses: actions/cache@v2
id: test-blocks-plots
with:
path: |
${{ github.workspace }}/.chia/blocks
${{ github.workspace }}/.chia/test-plots
key: 0.29.0
- name: Checkout test blocks and plots
if: steps.test-blocks-plots.outputs.cache-hit != 'true'
run: |
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf -
mkdir ${{ github.workspace }}/.chia
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
- name: Run install script
env:
INSTALL_PYTHON_VERSION: ${{ matrix.python-version }}
run: |
brew install boost
sh install.sh -d
# Omitted installing Timelord
- name: Test wallet-nft_wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_wallet.py
- name: Process coverage data
run: |
venv/bin/coverage combine --rcfile=.coveragerc .coverage.*
venv/bin/coverage xml --rcfile=.coveragerc -o coverage.xml
mkdir coverage_reports
cp .coverage "coverage_reports/.coverage.${{ env.JOB_FILE_NAME }}"
cp coverage.xml "coverage_reports/coverage.${{ env.JOB_FILE_NAME }}.xml"
venv/bin/coverage report --rcfile=.coveragerc --show-missing
- name: Publish coverage
uses: actions/upload-artifact@v3
with:
name: coverage
path: coverage_reports/*
if-no-files-found: error
#
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
#

View File

@ -24,7 +24,7 @@ jobs:
build:
name: Ubuntu wallet-did_wallet Test
runs-on: ${{ matrix.os }}
timeout-minutes: 30
timeout-minutes: 50
strategy:
fail-fast: false
max-parallel: 4
@ -67,7 +67,21 @@ jobs:
restore-keys: |
${{ runner.os }}-pip-
# Omitted checking out blocks and plots repo Chia-Network/test-cache
- name: Cache test blocks and plots
uses: actions/cache@v2
id: test-blocks-plots
with:
path: |
${{ github.workspace }}/.chia/blocks
${{ github.workspace }}/.chia/test-plots
key: 0.29.0
- name: Checkout test blocks and plots
if: steps.test-blocks-plots.outputs.cache-hit != 'true'
run: |
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf -
mkdir ${{ github.workspace }}/.chia
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
- name: Run install script
env:
@ -80,7 +94,7 @@ jobs:
- name: Test wallet-did_wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/did_wallet/test_did.py tests/wallet/did_wallet/test_did_rpc.py
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/did_wallet/test_did.py
- name: Process coverage data
run: |

View File

@ -0,0 +1,119 @@
#
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
#
name: Ubuntu wallet-nft_wallet Test
on:
push:
branches:
- 'long_lived/**'
- main
- 'release/**'
tags:
- '**'
pull_request:
branches:
- '**'
concurrency:
# SHA is added to the end if on `main` to let all main workflows run
group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/long_lived/')) && github.sha || '' }}
cancel-in-progress: true
jobs:
build:
name: Ubuntu wallet-nft_wallet Test
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
max-parallel: 4
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10']
os: [ubuntu-latest]
env:
CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet
JOB_FILE_NAME: tests_${{ matrix.os }}_python-${{ matrix.python-version }}_wallet-nft_wallet
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Python environment
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Cache npm
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache pip
uses: actions/cache@v3
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Cache test blocks and plots
uses: actions/cache@v2
id: test-blocks-plots
with:
path: |
${{ github.workspace }}/.chia/blocks
${{ github.workspace }}/.chia/test-plots
key: 0.29.0
- name: Checkout test blocks and plots
if: steps.test-blocks-plots.outputs.cache-hit != 'true'
run: |
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf -
mkdir ${{ github.workspace }}/.chia
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
- name: Run install script
env:
INSTALL_PYTHON_VERSION: ${{ matrix.python-version }}
run: |
sh install.sh -d
# Omitted installing Timelord
- name: Test wallet-nft_wallet code with pytest
run: |
. ./activate
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_wallet.py
- name: Process coverage data
run: |
venv/bin/coverage combine --rcfile=.coveragerc .coverage.*
venv/bin/coverage xml --rcfile=.coveragerc -o coverage.xml
mkdir coverage_reports
cp .coverage "coverage_reports/.coverage.${{ env.JOB_FILE_NAME }}"
cp coverage.xml "coverage_reports/coverage.${{ env.JOB_FILE_NAME }}.xml"
venv/bin/coverage report --rcfile=.coveragerc --show-missing
- name: Publish coverage
uses: actions/upload-artifact@v3
with:
name: coverage
path: coverage_reports/*
if-no-files-found: error
# Omitted resource usage check
#
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
#

View File

@ -197,6 +197,16 @@ jobs:
apt-get --yes update
apt-get install --yes git lsb-release sudo
# @TODO this step can be removed once Python 3.10 is supported
# Python 3.10 is now the default in bookworm, so install 3.9 specifically so install does not fail
- name: Prepare debian:bookworm
if: ${{ matrix.distribution.name == 'debian:bookworm' }}
env:
DEBIAN_FRONTEND: noninteractive
run: |
apt-get update -y
apt-get install -y python3.9-venv
- name: Prepare Fedora
if: ${{ matrix.distribution.type == 'fedora' }}
run: |

View File

@ -263,6 +263,7 @@ This release also includes several important performance improvements as a resul
- PlotNFT transactions via CLI (e.g. `chia plotnft join`) now accept a fee parameter, but it is not yet operable.
## 1.2.10 Chia blockchain 2021-10-25
We have some great improvements in this release: We launched our migration of keys to a common encrypted keyring.yaml file, and we secure this with an optional passphrase in both GUI and CLI. We've added a passphrase hint in case you forget your passphrase. More info on our [wiki](https://github.com/Chia-Network/chia-blockchain/wiki/Passphrase-Protected-Chia-Keys-and-Key-Storage-Migration). We also launched a new Chialisp compiler in clvm_tools_rs which substantially improves compile time for Chialisp developers. We also addressed a widely reported issue in which a system failure, such as a power outage, would require some farmers to sync their full node from zero. This release also includes several other improvements and fixes.

View File

@ -3,6 +3,7 @@ from typing import Any, Dict, Optional, Tuple
import click
from chia.cmds.plotnft import validate_fee
from chia.wallet.util.wallet_types import WalletType
from chia.wallet.transaction_sorting import SortKey
@ -391,3 +392,230 @@ def cancel_offer_cmd(wallet_rpc_port: Optional[int], fingerprint: int, id: str,
from .wallet_funcs import execute_with_wallet, cancel_offer
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, cancel_offer))
@wallet_cmd.group("did", short_help="DID related actions")
def did_cmd():
pass
@did_cmd.command("create", short_help="Create DID wallet")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-n", "--name", help="Set the DID wallet name", type=str)
@click.option(
"-a",
"--amount",
help="Set the DID amount in mojos. Value must be an odd number.",
type=int,
default=1,
show_default=True,
)
@click.option(
"-m",
"--fee",
help="Set the fees per transaction, in XCH.",
type=str,
default="0",
show_default=True,
callback=validate_fee,
)
def did_create_wallet_cmd(
wallet_rpc_port: Optional[int], fingerprint: int, name: Optional[str], amount: Optional[int], fee: Optional[int]
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, create_did_wallet
extra_params = {"amount": amount, "fee": fee, "name": name}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, create_did_wallet))
@did_cmd.command("set_name", short_help="Set DID wallet name")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-i", "--id", help="Id of the wallet to use", type=int, required=True)
@click.option("-n", "--name", help="Set the DID wallet name", type=str, required=True)
def did_wallet_name_cmd(wallet_rpc_port: Optional[int], fingerprint: int, id: int, name: str) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, did_set_wallet_name
extra_params = {"wallet_id": id, "name": name}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, did_set_wallet_name))
@wallet_cmd.group("nft", short_help="NFT related actions")
def nft_cmd():
pass
@nft_cmd.command("create", short_help="Create an NFT wallet")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
def nft_wallet_create_cmd(wallet_rpc_port: Optional[int], fingerprint: int) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, create_nft_wallet
extra_params: Dict[str, Any] = {}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, create_nft_wallet))
@nft_cmd.command("mint", short_help="Mint an NFT")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-i", "--id", help="Id of the NFT wallet to use", type=int, required=True)
@click.option("-aa", "--artist-address", help="Artist's backpayment address", type=str, required=True)
@click.option("-nh", "--hash", help="NFT content hash", type=str, required=True)
@click.option("-u", "--uris", help="Comma separated list of URIs", type=str, required=True)
@click.option(
"-m",
"--fee",
help="Set the fees per transaction, in XCH.",
type=str,
default="0",
show_default=True,
callback=validate_fee,
)
def nft_mint_cmd(
wallet_rpc_port: Optional[int],
fingerprint: int,
id: int,
artist_address: str,
hash: str,
uris: str,
fee: str,
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, mint_nft
extra_params = {
"wallet_id": id,
"artist_address": artist_address,
"hash": hash,
"uris": [u.strip() for u in uris.split(",")],
"fee": fee,
}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, mint_nft))
@nft_cmd.command("add_uri", short_help="Add an URI to an NFT")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-i", "--id", help="Id of the NFT wallet to use", type=int, required=True)
@click.option("-ni", "--nft-coin-id", help="Id of the NFT coin to add the URI to", type=str, required=True)
@click.option("-u", "--uri", help="URI to add to the NFT", type=str, required=True)
@click.option(
"-m",
"--fee",
help="Set the fees per transaction, in XCH.",
type=str,
default="0",
show_default=True,
callback=validate_fee,
)
def nft_add_uri_cmd(
wallet_rpc_port: Optional[int],
fingerprint: int,
id: int,
nft_coin_id: str,
uri: str,
fee: str,
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, add_uri_to_nft
extra_params = {
"wallet_id": id,
"nft_coin_id": nft_coin_id,
"uri": uri,
"fee": fee,
}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, add_uri_to_nft))
@nft_cmd.command("transfer", short_help="Transfer an NFT")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-i", "--id", help="Id of the NFT wallet to use", type=int, required=True)
@click.option("-ni", "--nft-coin-id", help="Id of the NFT coin to transfer", type=str, required=True)
@click.option("-aa", "--artist-address", help="Target artist's wallet address", type=str, required=True)
@click.option(
"-m",
"--fee",
help="Set the fees per transaction, in XCH.",
type=str,
default="0",
show_default=True,
callback=validate_fee,
)
def nft_transfer_cmd(
wallet_rpc_port: Optional[int],
fingerprint: int,
id: int,
nft_coin_id: str,
artist_address: str,
fee: str,
) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, transfer_nft
extra_params = {
"wallet_id": id,
"nft_coin_id": nft_coin_id,
"artist_address": artist_address,
"fee": fee,
}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, transfer_nft))
@nft_cmd.command("list", short_help="List the current NFTs")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
type=int,
default=None,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use", type=int)
@click.option("-i", "--id", help="Id of the NFT wallet to use", type=int, required=True)
def nft_list_cmd(wallet_rpc_port: Optional[int], fingerprint: int, id: int) -> None:
import asyncio
from .wallet_funcs import execute_with_wallet, list_nfts
extra_params = {"wallet_id": id}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, list_nfts))

View File

@ -643,3 +643,103 @@ async def execute_with_wallet(
print(f"Exception from 'wallet' {e}")
wallet_client.close()
await wallet_client.await_closed()
async def create_did_wallet(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
amount = args["amount"]
fee = args["fee"]
name = args["name"]
try:
response = await wallet_client.create_new_did_wallet(amount, fee, name)
wallet_id = response["wallet_id"]
my_did = response["my_did"]
print(f"Successfully created a DID wallet with name {name} and id {wallet_id} on key {fingerprint}")
print(f"Successfully created a DID {my_did} in the newly created DID wallet")
except Exception as e:
print(f"Failed to create DID wallet: {e}")
async def did_set_wallet_name(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
wallet_id = args["wallet_id"]
name = args["name"]
try:
await wallet_client.did_set_wallet_name(wallet_id, name)
print(f"Successfully set a new name for DID wallet with id {wallet_id}: {name}")
except Exception as e:
print(f"Failed to set DID wallet name: {e}")
async def create_nft_wallet(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
try:
response = await wallet_client.create_new_nft_wallet(None)
wallet_id = response["wallet_id"]
print(f"Successfully created an NFT wallet with id {wallet_id} on key {fingerprint}")
except Exception as e:
print(f"Failed to create NFT wallet: {e}")
async def mint_nft(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
try:
wallet_id = args["wallet_id"]
artist_address = args["artist_address"]
hash = args["hash"]
uris = args["uris"]
fee = args["fee"]
response = await wallet_client.mint_nft(wallet_id, artist_address, hash, uris, fee)
spend_bundle = response["spend_bundle"]
print(f"NFT minted Successfully with spend bundle: {spend_bundle}")
except Exception as e:
print(f"Failed to mint NFT: {e}")
async def add_uri_to_nft(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
try:
wallet_id = args["wallet_id"]
nft_coin_id = args["nft_coin_id"]
uri = args["uri"]
fee = args["fee"]
response = await wallet_client.add_uri_to_nft(wallet_id, nft_coin_id, uri, fee)
spend_bundle = response["spend_bundle"]
print(f"URI added successfully with spend bundle: {spend_bundle}")
except Exception as e:
print(f"Failed to add URI to NFT: {e}")
async def transfer_nft(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
try:
wallet_id = args["wallet_id"]
nft_coin_id = args["nft_coin_id"]
artist_address = args["artist_address"]
fee = args["fee"]
response = await wallet_client.transfer_nft(wallet_id, nft_coin_id, artist_address, fee)
spend_bundle = response["spend_bundle"]
print(f"NFT transferred successfully with spend bundle: {spend_bundle}")
except Exception as e:
print(f"Failed to transfer NFT: {e}")
async def list_nfts(args: Dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
wallet_id = args["wallet_id"]
try:
response = await wallet_client.list_nfts(wallet_id)
nft_list = response["nft_list"]
if len(nft_list) > 0:
from chia.wallet.nft_wallet.nft_info import NFTInfo
indent: str = " "
for n in nft_list:
nft = NFTInfo.from_json_dict(n)
print()
print(f"{'Launcher coin ID:'.ljust(23)} {nft.launcher_id}")
print(f"{'Current NFT coin ID:'.ljust(23)} {nft.nft_coin_id}")
print(f"{'NFT content hash:'.ljust(23)} {nft.data_hash}")
print(f"{'Current NFT version:'.ljust(23)} {nft.version}")
print()
print("URIs:")
for uri in nft.data_uris:
print(f"{indent}{uri}")
else:
print(f"No NFTs found for wallet with id {wallet_id} on key {fingerprint}")
except Exception as e:
print(f"Failed to list NFTs for wallet with id {wallet_id} on key {fingerprint}: {e}")

View File

@ -316,6 +316,7 @@ class MempoolManager:
# build removal list
removal_names: List[bytes32] = [spend.coin_id for spend in npc_result.conds.spends]
if set(removal_names) != set([s.name() for s in new_spend.removals()]):
# If you reach here it's probably because your program reveal doesn't match the coin's puzzle hash
return None, MempoolInclusionStatus.FAILED, Err.INVALID_SPEND_BUNDLE
additions = additions_for_npc(npc_result)

View File

@ -1,25 +1,28 @@
import asyncio
import dataclasses
import json
import logging
from pathlib import Path
from typing import Callable, Dict, List, Optional, Tuple, Set, Any
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
from blspy import PrivateKey, G1Element, G2Element
from blspy import G1Element, G2Element, PrivateKey
from chia.consensus.block_rewards import calculate_base_farmer_reward
from chia.data_layer.data_layer_wallet import DataLayerWallet
from chia.pools.pool_wallet import PoolWallet
from chia.pools.pool_wallet_info import create_pool_state, FARMING_TO_POOL, PoolWalletInfo, PoolState
from chia.pools.pool_wallet_info import FARMING_TO_POOL, PoolState, PoolWalletInfo, create_pool_state
from chia.protocols.protocol_message_types import ProtocolMessageTypes
from chia.server.outbound_message import NodeType, make_msg
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
from chia.types.announcement import Announcement
from chia.types.blockchain_format.coin import Coin, coin_as_list
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.spend_bundle import SpendBundle
from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash
from chia.util.byte_types import hexstr_to_bytes
from chia.util.ints import uint32, uint64, uint8
from chia.util.config import load_config
from chia.util.ints import uint8, uint32, uint64
from chia.util.keychain import KeyringIsLocked, bytes_to_mnemonic, generate_mnemonic
from chia.util.path import path_from_root
from chia.util.ws_message import WsRpcMessage, create_payload_dict
@ -33,6 +36,8 @@ from chia.wallet.derive_keys import (
match_address_to_sk,
)
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.nft_wallet.nft_puzzles import get_nft_info_from_puzzle
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.puzzle_drivers import PuzzleInfo
from chia.wallet.rl_wallet.rl_wallet import RLWallet
@ -43,7 +48,6 @@ from chia.wallet.util.transaction_type import TransactionType
from chia.wallet.util.wallet_types import AmountWithPuzzlehash, WalletType
from chia.wallet.wallet_info import WalletInfo
from chia.wallet.wallet_node import WalletNode
from chia.util.config import load_config
# Timeout for response from wallet/full node for sending a transaction
TIMEOUT = 30
@ -111,14 +115,25 @@ class WalletRpcApi:
"/cancel_offer": self.cancel_offer,
"/get_cat_list": self.get_cat_list,
# DID Wallet
"/did_set_wallet_name": self.did_set_wallet_name,
"/did_get_wallet_name": self.did_get_wallet_name,
"/did_update_recovery_ids": self.did_update_recovery_ids,
"/did_update_metadata": self.did_update_metadata,
"/did_get_pubkey": self.did_get_pubkey,
"/did_get_did": self.did_get_did,
"/did_recovery_spend": self.did_recovery_spend,
"/did_get_recovery_list": self.did_get_recovery_list,
"/did_get_metadata": self.did_get_metadata,
"/did_create_attest": self.did_create_attest,
"/did_get_information_needed_for_recovery": self.did_get_information_needed_for_recovery,
"/did_get_current_coin_info": self.did_get_current_coin_info,
"/did_create_backup_file": self.did_create_backup_file,
"/did_transfer_did": self.did_transfer_did,
# NFT Wallet
"/nft_mint_nft": self.nft_mint_nft,
"/nft_get_nfts": self.nft_get_nfts,
"/nft_transfer_nft": self.nft_transfer_nft,
"/nft_add_uri": self.nft_add_uri,
# RL wallet
"/rl_set_user_info": self.rl_set_user_info,
"/send_clawback_transaction:": self.send_clawback_transaction,
@ -510,6 +525,11 @@ class WalletRpcApi:
backup_dids.append(hexstr_to_bytes(d))
if len(backup_dids) > 0:
num_needed = uint64(request["num_of_backup_ids_needed"])
metadata: Dict[str, str] = {}
if "metadata" in request:
if type(request["metadata"]) is dict:
metadata = request["metadata"]
async with self.service.wallet_state_manager.lock:
did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_state_manager,
@ -517,7 +537,11 @@ class WalletRpcApi:
uint64(request["amount"]),
backup_dids,
uint64(num_needed),
metadata,
request.get("wallet_name", None),
uint64(request.get("fee", 0)),
)
my_did = did_wallet.get_my_DID()
return {
"success": True,
@ -529,7 +553,7 @@ class WalletRpcApi:
elif request["did_type"] == "recovery":
async with self.service.wallet_state_manager.lock:
did_wallet = await DIDWallet.create_new_did_wallet_from_recovery(
wallet_state_manager, main_wallet, request["filename"]
wallet_state_manager, main_wallet, request["backup_data"]
)
assert did_wallet.did_info.temp_coin is not None
assert did_wallet.did_info.temp_puzhash is not None
@ -553,7 +577,26 @@ class WalletRpcApi:
}
else: # undefined did_type
pass
elif request["wallet_type"] == "nft_wallet":
for wallet in self.service.wallet_state_manager.wallets.values():
if wallet.type() == WalletType.NFT:
# TODO Modify this for NFT1
log.info("NFT wallet already existed, skipping.")
return {
"success": True,
"type": wallet.type(),
"wallet_id": wallet.id(),
}
async with self.service.wallet_state_manager.lock:
nft_wallet: NFTWallet = await NFTWallet.create_new_nft_wallet(
wallet_state_manager,
main_wallet,
)
return {
"success": True,
"type": nft_wallet.type(),
"wallet_id": nft_wallet.id(),
}
elif request["wallet_type"] == "pool_wallet":
if request["mode"] == "new":
owner_puzzle_hash: bytes32 = await self.service.wallet_state_manager.main_wallet.get_puzzle_hash(True)
@ -1075,10 +1118,28 @@ class WalletRpcApi:
# Distributed Identities
##########################################################################################
async def did_set_wallet_name(self, request):
assert self.service.wallet_state_manager is not None
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
if wallet.type() == WalletType.DISTRIBUTED_ID:
await wallet.set_name(str(request["name"]))
return {"success": True, "wallet_id": wallet_id}
else:
return {"success": False, "error": f"Wallet id {wallet_id} is not a DID wallet"}
async def did_get_wallet_name(self, request):
assert self.service.wallet_state_manager is not None
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
name: str = await wallet.get_name()
return {"success": True, "wallet_id": wallet_id, "name": name}
async def did_update_recovery_ids(self, request):
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
recovery_list = []
success: bool = False
for _ in request["new_list"]:
recovery_list.append(hexstr_to_bytes(_))
if "num_verifications_required" in request:
@ -1088,9 +1149,27 @@ class WalletRpcApi:
async with self.service.wallet_state_manager.lock:
update_success = await wallet.update_recovery_list(recovery_list, new_amount_verifications_required)
# Update coin with new ID info
spend_bundle = await wallet.create_update_spend()
if update_success:
spend_bundle = await wallet.create_update_spend()
if spend_bundle is not None:
success = True
return {"success": success}
success = spend_bundle is not None and update_success
async def did_update_metadata(self, request):
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
metadata: Dict[str, str] = {}
success: bool = False
if "metadata" in request:
if type(request["metadata"]) is dict:
metadata = request["metadata"]
async with self.service.wallet_state_manager.lock:
update_success = await wallet.update_metadata(metadata)
# Update coin with new ID info
if update_success:
spend_bundle = await wallet.create_update_spend()
if spend_bundle is not None:
success = True
return {"success": success}
async def did_get_did(self, request):
@ -1115,21 +1194,31 @@ class WalletRpcApi:
return {
"success": True,
"wallet_id": wallet_id,
"recover_list": recover_hex_list,
"recovery_list": recover_hex_list,
"num_required": wallet.did_info.num_of_backup_ids_needed,
}
async def did_get_metadata(self, request):
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
metadata = json.loads(wallet.did_info.metadata)
return {
"success": True,
"wallet_id": wallet_id,
"metadata": metadata,
}
async def did_recovery_spend(self, request):
wallet_id = int(request["wallet_id"])
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
if len(request["attest_filenames"]) < wallet.did_info.num_of_backup_ids_needed:
if len(request["attest_data"]) < wallet.did_info.num_of_backup_ids_needed:
return {"success": False, "reason": "insufficient messages"}
async with self.service.wallet_state_manager.lock:
(
info_list,
message_spend_bundle,
) = await wallet.load_attest_files_for_recovery_spend(request["attest_filenames"])
) = await wallet.load_attest_files_for_recovery_spend(request["attest_data"])
if "pubkey" in request:
pubkey = G1Element.from_bytes(hexstr_to_bytes(request["pubkey"]))
@ -1165,14 +1254,17 @@ class WalletRpcApi:
info = await wallet.get_info_for_recovery()
coin = hexstr_to_bytes(request["coin_name"])
pubkey = G1Element.from_bytes(hexstr_to_bytes(request["pubkey"]))
spend_bundle = await wallet.create_attestment(
coin, hexstr_to_bytes(request["puzhash"]), pubkey, request["filename"]
spend_bundle, attest_data = await wallet.create_attestment(
coin,
hexstr_to_bytes(request["puzhash"]),
pubkey,
)
if spend_bundle is not None:
if info is not None and spend_bundle is not None:
return {
"success": True,
"message_spend_bundle": bytes(spend_bundle).hex(),
"info": [info[0].hex(), info[1].hex(), info[2]],
"attest_data": attest_data,
}
else:
return {"success": False}
@ -1192,14 +1284,113 @@ class WalletRpcApi:
"backup_dids": did_wallet.did_info.backup_ids,
}
async def did_get_current_coin_info(self, request):
wallet_id = int(request["wallet_id"])
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
my_did = did_wallet.get_my_DID()
did_coin_threeple = await did_wallet.get_info_for_recovery()
assert my_did is not None
assert did_coin_threeple is not None
return {
"success": True,
"wallet_id": wallet_id,
"my_did": my_did,
"did_parent": did_coin_threeple[0],
"did_innerpuz": did_coin_threeple[1],
"did_amount": did_coin_threeple[2],
}
async def did_create_backup_file(self, request):
wallet_id = int(request["wallet_id"])
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
return {"wallet_id": wallet_id, "success": True, "backup_data": did_wallet.create_backup()}
async def did_transfer_did(self, request):
assert self.service.wallet_state_manager is not None
if await self.service.wallet_state_manager.synced() is False:
raise ValueError("Wallet needs to be fully synced.")
wallet_id = int(request["wallet_id"])
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
puzzle_hash: bytes32 = decode_puzzle_hash(request["inner_address"])
async with self.service.wallet_state_manager.lock:
txs: TransactionRecord = await did_wallet.transfer_did(
puzzle_hash, uint64(request.get("fee", 0)), request.get("with_recovery_info", True)
)
return {
"success": True,
"transaction": txs.to_json_dict_convenience(self.service.config),
"transaction_id": txs.name,
}
##########################################################################################
# NFT Wallet
##########################################################################################
async def nft_mint_nft(self, request) -> Dict:
log.debug("Got minting RPC request: %s", request)
wallet_id = uint32(request["wallet_id"])
assert self.service.wallet_state_manager
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
assert nft_wallet.type() == WalletType.NFT.value, nft_wallet.type()
address = request.get("artist_address")
if isinstance(address, str):
puzzle_hash = decode_puzzle_hash(address)
elif address is None:
puzzle_hash = await nft_wallet.standard_wallet.get_new_puzzlehash()
else:
puzzle_hash = address
metadata = Program.to(
[
("u", request["uris"]),
("h", hexstr_to_bytes(request["hash"])),
]
)
fee = uint64(request.get("fee", 0))
spend_bundle = await nft_wallet.generate_new_nft(metadata, puzzle_hash, fee=fee)
return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle}
async def nft_get_nfts(self, request) -> Dict:
wallet_id = uint32(request["wallet_id"])
assert self.service.wallet_state_manager is not None
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
nfts = nft_wallet.get_current_nfts()
nft_info_list = []
for nft in nfts:
nft_info_list.append(get_nft_info_from_puzzle(nft.full_puzzle, nft.coin))
return {"wallet_id": wallet_id, "success": True, "nft_list": nft_info_list}
async def nft_transfer_nft(self, request):
assert self.service.wallet_state_manager is not None
wallet_id = uint32(request["wallet_id"])
address = request["target_address"]
if isinstance(address, str):
puzzle_hash = decode_puzzle_hash(address)
else:
return dict(success=False, error="target_address parameter missing")
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
try:
wallet_id = int(request["wallet_id"])
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
did_wallet.create_backup(request["filename"])
return {"wallet_id": wallet_id, "success": True}
except Exception:
return {"wallet_id": wallet_id, "success": False}
nft_coin_info = nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(request["nft_coin_id"]))
fee = uint64(request.get("fee", 0))
spend_bundle = await nft_wallet.transfer_nft(nft_coin_info, puzzle_hash, fee=fee)
return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle}
except Exception as e:
log.exception(f"Failed to transfer NFT: {e}")
return {"success": False, "error": str(e)}
async def nft_add_uri(self, request) -> Dict:
assert self.service.wallet_state_manager is not None
wallet_id = uint32(request["wallet_id"])
uri = request["uri"]
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
try:
nft_coin_info = nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(request["nft_coin_id"]))
fee = uint64(request.get("fee", 0))
spend_bundle = await nft_wallet.update_metadata(nft_coin_info, uri, fee=fee)
return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle}
except Exception as e:
log.exception(f"Failed to update NFT metadata: {e}")
return {"success": False, "error": str(e)}
##########################################################################################
# Rate Limited Wallet

View File

@ -241,18 +241,73 @@ class WalletRpcClient(RpcClient):
return [Coin.from_json_dict(coin) for coin in response["coins"]]
# DID wallet
async def create_new_did_wallet(self, amount):
async def create_new_did_wallet(
self,
amount: int,
fee: int = 0,
name: Optional[str] = "DID Wallet",
backup_ids: List[str] = [],
required_num: int = 0,
) -> Dict:
request: Dict[str, Any] = {
"wallet_type": "did_wallet",
"did_type": "new",
"backup_dids": [],
"num_of_backup_ids_needed": 0,
"backup_dids": backup_ids,
"num_of_backup_ids_needed": required_num,
"amount": amount,
"fee": fee,
"wallet_name": name,
}
response = await self.fetch("create_new_wallet", request)
return response
async def create_new_did_wallet_from_recovery(self, filename):
async def get_did_id(self, wallet_id: int) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
}
response = await self.fetch("did_get_did", request)
return response
async def create_did_backup_file(self, wallet_id: int, filename: str) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"filename": filename,
}
response = await self.fetch("did_create_backup_file", request)
return response
async def update_did_recovery_list(self, wallet_id: int, recovery_list: List[str], num_verification: int) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"new_list": recovery_list,
"num_verifications_required": num_verification,
}
response = await self.fetch("did_update_recovery_ids", request)
return response
async def get_did_recovery_list(self, wallet_id: int) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
}
response = await self.fetch("did_get_recovery_list", request)
return response
async def update_did_metadata(self, wallet_id: int, metadata: Dict) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"metadata": metadata,
}
response = await self.fetch("did_update_metadata", request)
return response
async def get_did_metadata(self, wallet_id: int) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
}
response = await self.fetch("did_get_metadata", request)
return response
async def create_new_did_wallet_from_recovery(self, filename: str) -> Dict:
request: Dict[str, Any] = {
"wallet_type": "did_wallet",
"did_type": "recovery",
@ -261,7 +316,9 @@ class WalletRpcClient(RpcClient):
response = await self.fetch("create_new_wallet", request)
return response
async def did_create_attest(self, wallet_id, coin_name, pubkey, puzhash, file_name):
async def did_create_attest(
self, wallet_id: int, coin_name: str, pubkey: str, puzhash: str, file_name: str
) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"coin_name": coin_name,
@ -272,7 +329,7 @@ class WalletRpcClient(RpcClient):
response = await self.fetch("did_create_attest", request)
return response
async def did_recovery_spend(self, wallet_id, attest_filenames):
async def did_recovery_spend(self, wallet_id: int, attest_filenames: str) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"attest_filenames": attest_filenames,
@ -280,6 +337,26 @@ class WalletRpcClient(RpcClient):
response = await self.fetch("did_recovery_spend", request)
return response
async def did_transfer_did(self, wallet_id: int, address: str, fee: int, with_recovery: bool) -> Dict:
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"inner_address": address,
"fee": fee,
"with_recovery_info": with_recovery,
}
response = await self.fetch("did_transfer_did", request)
return response
async def did_set_wallet_name(self, wallet_id: int, name: str) -> Dict:
request = {"wallet_id": wallet_id, "name": name}
response = await self.fetch("did_set_wallet_name", request)
return response
async def did_get_wallet_name(self, wallet_id: int) -> Dict:
request = {"wallet_id": wallet_id}
response = await self.fetch("did_get_wallet_name", request)
return response
# TODO: test all invocations of create_new_pool_wallet with new fee arg.
async def create_new_pool_wallet(
self,
@ -498,6 +575,46 @@ class WalletRpcClient(RpcClient):
async def cancel_offer(self, trade_id: bytes32, fee=uint64(0), secure: bool = True):
await self.fetch("cancel_offer", {"trade_id": trade_id.hex(), "secure": secure, "fee": fee})
# NFT wallet
async def create_new_nft_wallet(self, did_wallet_id):
request: Dict[str, Any] = {
"wallet_type": "nft_wallet",
"did_wallet_id": did_wallet_id,
}
response = await self.fetch("create_new_wallet", request)
return response
async def mint_nft(self, wallet_id, artist_address, hash, uris, fee):
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"artist_address": artist_address,
"hash": hash,
"uris": uris,
"fee": fee,
}
response = await self.fetch("nft_mint_nft", request)
return response
async def add_uri_to_nft(self, wallet_id, nft_coin_id, uri, fee):
request: Dict[str, Any] = {"wallet_id": wallet_id, "nft_coin_id": nft_coin_id, "uri": uri, "fee": fee}
response = await self.fetch("nft_add_uri", request)
return response
async def transfer_nft(self, wallet_id, nft_coin_id, artist_address, fee):
request: Dict[str, Any] = {
"wallet_id": wallet_id,
"nft_coin_id": nft_coin_id,
"target_address": artist_address,
"fee": fee,
}
response = await self.fetch("nft_transfer_nft", request)
return response
async def list_nfts(self, wallet_id):
request: Dict[str, Any] = {"wallet_id": wallet_id}
response = await self.fetch("nft_get_nfts", request)
return response
# DataLayer
async def create_new_dl(self, root: bytes32, fee: uint64) -> Tuple[List[TransactionRecord], bytes32]:
request = {"root": root.hex(), "fee": fee}

View File

@ -1,6 +1,5 @@
from dataclasses import dataclass
from typing import List
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import SerializedProgram, INFINITE_COST
from chia.util.chain_utils import additions_for_solution, fee_for_solution

View File

@ -41,6 +41,14 @@ def match_cat_puzzle(puzzle: Program) -> Tuple[bool, Iterator[Program]]:
return False, iter(())
def get_innerpuzzle_from_puzzle(puzzle: Program) -> Program:
mod, curried_args = puzzle.uncurry()
if mod == CAT_MOD:
return curried_args.rest().rest().first()
else:
raise ValueError("Not a CAT puzzle")
def construct_cat_puzzle(mod_code: Program, limitations_program_hash: bytes32, inner_puzzle: Program) -> Program:
"""
Given an inner puzzle hash and tail hash calculate a puzzle program for a specific cc.

View File

@ -732,7 +732,6 @@ class CATWallet:
max_send = await self.get_max_send_amount()
if payment_sum > max_send:
raise ValueError(f"Can't send more than {max_send} in a single transaction")
unsigned_spend_bundle, chia_tx = await self.generate_unsigned_spendbundle(
payments,
fee,
@ -741,7 +740,6 @@ class CATWallet:
puzzle_announcements_to_consume=puzzle_announcements_to_consume,
)
spend_bundle = await self.sign(unsigned_spend_bundle)
# TODO add support for array in stored records
tx_list = [
TransactionRecord(

View File

@ -13,7 +13,7 @@ from chia.types.blockchain_format.coin import Coin
@dataclass(frozen=True)
class DIDInfo(Streamable):
origin_coin: Optional[Coin] # Coin ID of this coin is our DID
backup_ids: List[bytes]
backup_ids: List[bytes32]
num_of_backup_ids_needed: uint64
parent_info: List[Tuple[bytes32, Optional[LineageProof]]] # {coin.name(): LineageProof}
current_inner: Optional[Program] # represents a Program as bytes
@ -21,3 +21,4 @@ class DIDInfo(Streamable):
temp_puzhash: Optional[bytes32]
temp_pubkey: Optional[bytes]
sent_recovery_transaction: bool
metadata: str # JSON of the user defined metadata

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
from clvm_tools import binutils
from clvm_tools.binutils import assemble
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.blockchain_format.program import Program
from typing import List, Optional, Tuple
from typing import List, Optional, Tuple, Iterator, Dict
from blspy import G1Element
from chia.types.blockchain_format.coin import Coin
from chia.types.coin_spend import CoinSpend
@ -10,50 +10,95 @@ from chia.wallet.puzzles.load_clvm import load_clvm
from chia.types.condition_opcodes import ConditionOpcode
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer.clvm")
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
DID_INNERPUZ_MOD = load_clvm("did_innerpuz.clvm")
SINGLETON_LAUNCHER = load_clvm("singleton_launcher.clvm")
SINGLETON_MOD_HASH = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
LAUNCHER_PUZZLE_HASH = SINGLETON_LAUNCHER.get_tree_hash()
DID_INNERPUZ_MOD_HASH = DID_INNERPUZ_MOD.get_tree_hash()
def create_innerpuz(pubkey: bytes, identities: List[bytes], num_of_backup_ids_needed: uint64) -> Program:
backup_ids_hash = Program(Program.to(identities)).get_tree_hash()
# MOD_HASH MY_PUBKEY RECOVERY_DID_LIST_HASH NUM_VERIFICATIONS_REQUIRED
return DID_INNERPUZ_MOD.curry(pubkey, backup_ids_hash, num_of_backup_ids_needed)
def create_innerpuz(
p2_puzzle: Program,
recovery_list: List[bytes32],
num_of_backup_ids_needed: uint64,
launcher_id: bytes32,
metadata: Program = Program.to([]),
) -> Program:
"""
Create DID inner puzzle
:param p2_puzzle: Standard P2 puzzle
:param recovery_list: A list of DIDs used for the recovery
:param num_of_backup_ids_needed: Need how many DIDs for the recovery
:param launcher_id: ID of the launch coin
:param metadata: DID customized metadata
:return: DID inner puzzle
"""
backup_ids_hash = Program(Program.to(recovery_list)).get_tree_hash()
singleton_struct = Program.to((SINGLETON_MOD_HASH, (launcher_id, LAUNCHER_PUZZLE_HASH)))
return DID_INNERPUZ_MOD.curry(p2_puzzle, backup_ids_hash, num_of_backup_ids_needed, singleton_struct, metadata)
def create_fullpuz(innerpuz: Program, genesis_id: bytes32) -> Program:
def get_inner_puzhash_by_p2(
p2_puzhash: bytes32,
recovery_list: List[bytes32],
num_of_backup_ids_needed: uint64,
launcher_id: bytes32,
metadata: Program = Program.to([]),
) -> bytes32:
"""
Calculate DID inner puzzle hash based on a P2 puzzle hash
:param p2_puzhash: P2 puzzle hash
:param recovery_list: A list of DIDs used for the recovery
:param num_of_backup_ids_needed: Need how many DIDs for the recovery
:param launcher_id: ID of the launch coin
:param metadata: DID customized metadata
:return: DID inner puzzle hash
"""
backup_ids_hash = Program(Program.to(recovery_list)).get_tree_hash()
singleton_struct = Program.to((SINGLETON_MOD_HASH, (launcher_id, LAUNCHER_PUZZLE_HASH)))
return DID_INNERPUZ_MOD.curry(
p2_puzhash, backup_ids_hash, num_of_backup_ids_needed, singleton_struct, metadata
).get_tree_hash(p2_puzhash)
def create_fullpuz(innerpuz: Program, launcher_id: bytes32) -> Program:
"""
Create a full puzzle of DID
:param innerpuz: DID inner puzzle
:param launcher_id:
:return: DID full puzzle
"""
mod_hash = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
# singleton_struct = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
singleton_struct = Program.to((mod_hash, (genesis_id, LAUNCHER_PUZZLE.get_tree_hash())))
singleton_struct = Program.to((mod_hash, (launcher_id, LAUNCHER_PUZZLE.get_tree_hash())))
return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, innerpuz)
def get_pubkey_from_innerpuz(innerpuz: Program) -> G1Element:
ret = uncurry_innerpuz(innerpuz)
if ret is not None:
pubkey_program = ret[0]
else:
raise ValueError("Unable to extract pubkey")
pubkey = G1Element.from_bytes(pubkey_program.as_atom())
return pubkey
def is_did_innerpuz(inner_f: Program):
def is_did_innerpuz(inner_f: Program) -> bool:
"""
You may want to generalize this if different `CAT_MOD` templates are supported.
Check if a puzzle is a DID inner mode
:param inner_f: puzzle
:return: Boolean
"""
return inner_f == DID_INNERPUZ_MOD
def is_did_core(inner_f: Program):
def is_did_core(inner_f: Program) -> bool:
"""
Check if a puzzle is a singleton mod
:param inner_f: puzzle
:return: Boolean
"""
return inner_f == SINGLETON_TOP_LAYER_MOD
def uncurry_innerpuz(puzzle: Program) -> Optional[Tuple[Program, Program]]:
def uncurry_innerpuz(puzzle: Program) -> Optional[Tuple[Program, Program, Program, Program, Program]]:
"""
Take a puzzle and return `None` if it's not a `CAT_MOD` cc, or
a triple of `mod_hash, genesis_coin_checker, inner_puzzle` if it is.
Uncurry a DID inner puzzle
:param puzzle: DID puzzle
:return: Curried parameters
"""
r = puzzle.uncurry()
if r is None:
@ -62,11 +107,16 @@ def uncurry_innerpuz(puzzle: Program) -> Optional[Tuple[Program, Program]]:
if not is_did_innerpuz(inner_f):
return None
pubkey, id_list, num_of_backup_ids_needed = list(args.as_iter())
return pubkey, id_list
p2_puzzle, id_list, num_of_backup_ids_needed, singleton_struct, metadata = list(args.as_iter())
return p2_puzzle, id_list, num_of_backup_ids_needed, singleton_struct, metadata
def get_innerpuzzle_from_puzzle(puzzle: Program) -> Optional[Program]:
"""
Extract the inner puzzle of a singleton
:param puzzle: Singleton puzzle
:return: Inner puzzle
"""
r = puzzle.uncurry()
if r is None:
return None
@ -77,13 +127,36 @@ def get_innerpuzzle_from_puzzle(puzzle: Program) -> Optional[Program]:
return INNER_PUZZLE
def create_recovery_message_puzzle(recovering_coin_id: bytes32, newpuz: bytes32, pubkey: G1Element):
puzstring = f"(q . ((0x{ConditionOpcode.CREATE_COIN_ANNOUNCEMENT.hex()} 0x{recovering_coin_id.hex()}) (0x{ConditionOpcode.AGG_SIG_UNSAFE.hex()} 0x{bytes(pubkey).hex()} 0x{newpuz.hex()})))" # noqa
puz = binutils.assemble(puzstring)
return Program.to(puz)
def create_recovery_message_puzzle(recovering_coin_id: bytes32, newpuz: bytes32, pubkey: G1Element) -> Program:
"""
Create attestment message puzzle
:param recovering_coin_id: ID of the DID coin needs to recover
:param newpuz: New wallet puzzle hash
:param pubkey: New wallet pubkey
:return: Message puzzle
"""
return Program.to(
(
1,
[
[ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, recovering_coin_id],
[ConditionOpcode.AGG_SIG_UNSAFE, bytes(pubkey), newpuz],
],
)
)
def create_spend_for_message(parent_of_message, recovering_coin, newpuz, pubkey):
def create_spend_for_message(
parent_of_message: bytes32, recovering_coin: bytes32, newpuz: bytes32, pubkey: G1Element
) -> CoinSpend:
"""
Create a CoinSpend for a atestment
:param parent_of_message: Parent coin ID
:param recovering_coin: ID of the DID coin needs to recover
:param newpuz: New wallet puzzle hash
:param pubkey: New wallet pubkey
:return: CoinSpend
"""
puzzle = create_recovery_message_puzzle(recovering_coin, newpuz, pubkey)
coin = Coin(parent_of_message, puzzle.get_tree_hash(), uint64(0))
solution = Program.to([])
@ -91,10 +164,58 @@ def create_spend_for_message(parent_of_message, recovering_coin, newpuz, pubkey)
return coinsol
# inspect puzzle and check it is a DID puzzle
def check_is_did_puzzle(puzzle: Program):
def match_did_puzzle(puzzle: Program) -> Tuple[bool, Iterator[Program]]:
"""
Given a puzzle test if it's a DID, if it is, return the curried arguments
:param puzzle: Puzzle
:return: Curried parameters
"""
try:
mod, curried_args = puzzle.uncurry()
if mod == SINGLETON_TOP_LAYER_MOD:
mod, curried_args = curried_args.rest().first().uncurry()
if mod == DID_INNERPUZ_MOD:
return True, curried_args.as_iter()
except Exception:
import traceback
print(f"exception: {traceback.format_exc()}")
return False, iter(())
return False, iter(())
def check_is_did_puzzle(puzzle: Program) -> bool:
"""
Check if a puzzle is a DID puzzle
:param puzzle: Puzzle
:return: Boolean
"""
r = puzzle.uncurry()
if r is None:
return r
inner_f, args = r
return is_did_core(inner_f)
def metadata_to_program(metadata: Dict) -> Program:
"""
Convert the metadata dict to a Chialisp program
:param metadata: User defined metadata
:return: Chialisp program
"""
kv_list = []
for key, value in metadata.items():
kv_list.append((assemble(key), assemble(value)))
return Program.to(kv_list)
def program_to_metadata(program: Program) -> Dict:
"""
Convert a program to a metadata dict
:param program: Chialisp program contains the metadata
:return: Metadata dict
"""
metadata = {}
for key, val in program.as_python():
metadata[str(key, "utf-8")] = str(val, "utf-8")
return metadata

View File

View File

@ -0,0 +1,50 @@
from dataclasses import dataclass
from typing import List
from chia.util.ints import uint64
from chia.util.streamable import Streamable, streamable
@streamable
@dataclass(frozen=True)
class NFTInfo(Streamable):
"""NFT Info for displaying NFT on the UI"""
launcher_id: str
"""Launcher coin ID"""
nft_coin_id: str
"""Current NFT coin ID"""
did_owner: str
"""Owner DID"""
royalty: uint64
"""Percentage of the transaction fee paid to the author, e.g. 1000 = 1%"""
data_uris: List[str]
""" A list of content URIs"""
data_hash: str
"""Hash of the content"""
metadata_uris: List[str]
"""A list of metadata URIs"""
metadata_hash: str
"""Hash of the metadata"""
license_uris: List[str]
"""A list of license URIs"""
license_hash: str
"""Hash of the license"""
version: str
"""Current NFT version"""
edition_count: uint64
"""How many NFTs in the current series"""
edition_number: uint64
"""Number of the current NFT in the series"""

View File

@ -0,0 +1,101 @@
import logging
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint64
from chia.wallet.nft_wallet.nft_info import NFTInfo
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
from chia.wallet.puzzles.load_clvm import load_clvm
log = logging.getLogger(__name__)
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm")
LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash()
SINGLETON_MOD_HASH = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
NFT_TRANSFER_PROGRAM = load_clvm("nft_transfer_program.clvm")
OFFER_MOD = load_clvm("settlement_payments.clvm")
NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.clvm")
def create_nft_layer_puzzle_with_curry_params(
metadata: Program, metadata_updater_hash: bytes32, inner_puzzle: Program
) -> Program:
"""Curries params into nft_state_layer.clvm
Args to curry:
NFT_STATE_LAYER_MOD_HASH
METADATA
METADATA_UPDATER_PUZZLE_HASH
INNER_PUZZLE"""
log.debug(
"Creating nft layer puzzle curry: mod_hash: %s, metadata: %r, metadata_hash: %s",
NFT_STATE_LAYER_MOD_HASH,
metadata,
metadata_updater_hash,
)
return NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, metadata_updater_hash, inner_puzzle)
def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Program) -> Program:
log.debug(
"Creating full NFT puzzle with inner puzzle: \n%r\n%r",
singleton_id,
inner_puzzle.get_tree_hash(),
)
singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH)))
full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, inner_puzzle)
log.debug("Created NFT full puzzle with inner: %s", full_puzzle.get_tree_hash())
return full_puzzle
def create_full_puzzle(
singleton_id: bytes32, metadata: Program, metadata_updater_puzhash: bytes32, inner_puzzle: Program
) -> Program:
log.debug(
"Creating full NFT puzzle with: \n%r\n%r\n%r\n%r",
singleton_id,
metadata.get_tree_hash(),
metadata_updater_puzhash,
inner_puzzle.get_tree_hash(),
)
singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH)))
singleton_inner_puzzle = create_nft_layer_puzzle_with_curry_params(metadata, metadata_updater_puzhash, inner_puzzle)
full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, singleton_inner_puzzle)
log.debug("Created NFT full puzzle: %s", full_puzzle.get_tree_hash())
return full_puzzle
def get_nft_info_from_puzzle(puzzle: Program, nft_coin: Coin) -> NFTInfo:
"""
Extract NFT info from a full puzzle
:param puzzle: NFT full puzzle
:param nft_coin: NFT coin
:return: NFTInfo
"""
# TODO Update this method after the NFT code finalized
uncurried_nft: UncurriedNFT = UncurriedNFT.uncurry(puzzle)
data_uris = []
for uri in uncurried_nft.data_uris.as_python():
data_uris.append(str(uri, "utf-8"))
nft_info = NFTInfo(
uncurried_nft.singleton_launcher_id.as_python().hex().upper(),
nft_coin.name().hex().upper(),
uncurried_nft.owner_did.as_python().hex().upper(),
uint64(uncurried_nft.trade_price_percentage.as_int()),
data_uris,
uncurried_nft.data_hash.as_python().hex().upper(),
[],
"",
[],
"",
"NFT0",
uint64(1),
uint64(1),
)
return nft_info

View File

@ -0,0 +1,564 @@
import json
import logging
import time
from dataclasses import dataclass
from secrets import token_bytes
from typing import Any, Dict, List, Optional, Set, Type, TypeVar
from blspy import AugSchemeMPL, G1Element, G2Element
from clvm.casts import int_from_bytes, int_to_bytes
from chia.clvm.singleton import SINGLETON_TOP_LAYER_MOD
from chia.protocols.wallet_protocol import CoinState
from chia.server.outbound_message import NodeType
from chia.server.ws_connection import WSChiaConnection
from chia.types.announcement import Announcement
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend
from chia.types.spend_bundle import SpendBundle
from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict
from chia.util.ints import uint8, uint32, uint64, uint128
from chia.util.streamable import Streamable, streamable
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.nft_wallet import nft_puzzles
from chia.wallet.nft_wallet.nft_puzzles import LAUNCHER_PUZZLE, NFT_METADATA_UPDATER, NFT_STATE_LAYER_MOD_HASH
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
from chia.wallet.puzzles.load_clvm import load_clvm
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
DEFAULT_HIDDEN_PUZZLE_HASH,
calculate_synthetic_secret_key,
puzzle_for_pk,
solution_for_conditions,
)
from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition
from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.util.compute_memos import compute_memos
from chia.wallet.util.debug_spend_bundle import disassemble
from chia.wallet.util.transaction_type import TransactionType
from chia.wallet.util.wallet_types import WalletType
from chia.wallet.wallet import Wallet
from chia.wallet.wallet_info import WalletInfo
_T_NFTWallet = TypeVar("_T_NFTWallet", bound="NFTWallet")
OFFER_MOD = load_clvm("settlement_payments.clvm")
@streamable
@dataclass(frozen=True)
class NFTCoinInfo(Streamable):
coin: Coin
lineage_proof: LineageProof
full_puzzle: Program
@streamable
@dataclass(frozen=True)
class NFTWalletInfo(Streamable):
my_nft_coins: List[NFTCoinInfo]
did_wallet_id: Optional[uint32] = None
def create_fullpuz(innerpuz: Program, genesis_id: bytes32) -> Program:
mod_hash = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
# singleton_struct = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
singleton_struct = Program.to((mod_hash, (genesis_id, LAUNCHER_PUZZLE.get_tree_hash())))
return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, innerpuz)
class NFTWallet:
wallet_state_manager: Any
log: logging.Logger
wallet_info: WalletInfo
nft_wallet_info: NFTWalletInfo
standard_wallet: Wallet
wallet_id: int
@classmethod
async def create_new_nft_wallet(
cls: Type[_T_NFTWallet],
wallet_state_manager: Any,
wallet: Wallet,
did_wallet_id: uint32 = None,
name: str = "",
in_transaction: bool = False,
) -> _T_NFTWallet:
"""
This must be called under the wallet state manager lock
"""
self = cls()
self.standard_wallet = wallet
self.log = logging.getLogger(name if name else __name__)
self.wallet_state_manager = wallet_state_manager
self.nft_wallet_info = NFTWalletInfo([], did_wallet_id)
info_as_string = json.dumps(self.nft_wallet_info.to_json_dict())
wallet_info = await wallet_state_manager.user_store.create_wallet(
"NFT Wallet" if not name else name,
uint32(WalletType.NFT.value),
info_as_string,
in_transaction=in_transaction,
)
if wallet_info is None:
raise ValueError("Internal Error")
self.wallet_info = wallet_info
self.wallet_id = self.wallet_info.id
self.log.debug("NFT wallet id: %r and standard wallet id: %r", self.wallet_id, self.standard_wallet.wallet_id)
await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id, in_transaction=in_transaction)
self.log.debug("Generated a new NFT wallet: %s", self.__dict__)
if not did_wallet_id:
# default profile wallet
self.log.debug("Standard NFT wallet created")
else:
# TODO: handle DID wallet puzhash
raise NotImplementedError()
return self
@classmethod
async def create(
cls: Type[_T_NFTWallet],
wallet_state_manager: Any,
wallet: Wallet,
wallet_info: WalletInfo,
name: Optional[str] = None,
) -> _T_NFTWallet:
self = cls()
self.log = logging.getLogger(name if name else __name__)
self.wallet_state_manager = wallet_state_manager
self.wallet_info = wallet_info
self.wallet_id = wallet_info.id
self.standard_wallet = wallet
self.wallet_info = wallet_info
self.nft_wallet_info = NFTWalletInfo.from_json_dict(json.loads(wallet_info.data))
return self
@classmethod
def type(cls) -> uint8:
return uint8(WalletType.NFT)
async def get_new_puzzle(self) -> Program:
self.log.debug("Getting new puzzle for NFT wallet: %s", self.id())
return self.puzzle_for_pk((await self.wallet_state_manager.get_unused_derivation_record(self.id())).pubkey)
def id(self) -> uint32:
return self.wallet_info.id
async def get_confirmed_balance(self, record_list=None) -> uint128:
"""The NFT wallet doesn't really have a balance."""
return uint128(0)
async def get_unconfirmed_balance(self, record_list=None) -> uint128:
"""The NFT wallet doesn't really have a balance."""
return uint128(0)
async def get_spendable_balance(self, unspent_records=None) -> uint128:
"""The NFT wallet doesn't really have a balance."""
return uint128(0)
async def get_pending_change_balance(self) -> uint64:
return uint64(0)
async def get_max_send_amount(self, records=None):
"""This is the confirmed balance, which we set to 0 as the NFT wallet doesn't have one."""
return uint128(0)
def get_nft_coin_by_id(self, nft_coin_id: bytes32) -> NFTCoinInfo:
for nft_coin in self.nft_wallet_info.my_nft_coins:
if nft_coin.coin.name() == nft_coin_id:
return nft_coin
raise KeyError(f"Couldn't find coin with id: {nft_coin_id}")
async def add_nft_coin(self, coin: Coin, spent_height: uint32, in_transaction: bool) -> None:
await self.coin_added(coin, spent_height, in_transaction=in_transaction)
async def coin_added(self, coin: Coin, height: uint32, in_transaction: bool) -> None:
"""Notification from wallet state manager that wallet has been received."""
self.log.info(f"NFT wallet %s has been notified that {coin} was added", self.wallet_info.name)
for coin_info in self.nft_wallet_info.my_nft_coins:
if coin_info.coin == coin:
return
wallet_node = self.wallet_state_manager.wallet_node
server = wallet_node.server
full_nodes: Dict[bytes32, WSChiaConnection] = server.connection_by_type.get(NodeType.FULL_NODE, {})
cs: Optional[CoinSpend] = None
coin_states: Optional[List[CoinState]] = await self.wallet_state_manager.wallet_node.get_coin_state(
[coin.parent_coin_info]
)
if not coin_states:
# farm coin
return
assert coin_states
parent_coin = coin_states[0].coin
for node_id in full_nodes:
node = server.all_connections[node_id]
cs = await wallet_node.fetch_puzzle_solution(node, height, parent_coin)
if cs is not None:
break
assert cs is not None
await self.puzzle_solution_received(cs, in_transaction=in_transaction)
async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: bool) -> None:
self.log.debug("Puzzle solution received to wallet: %s", self.wallet_info)
coin_name = coin_spend.coin.name()
puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal))
solution: Program = Program.from_bytes(bytes(coin_spend.solution)).rest().rest().first().first()
# At this point, the puzzle must be a NFT puzzle.
# This method will be called only when the walle state manager uncurried this coin as a NFT puzzle.
uncurried_nft = UncurriedNFT.uncurry(puzzle)
self.log.info(
f"found the info for NFT coin {coin_name} {uncurried_nft.inner_puzzle} {uncurried_nft.singleton_struct}"
)
singleton_id = bytes32(uncurried_nft.singleton_launcher_id.atom)
metadata = uncurried_nft.metadata
new_inner_puzzle = None
parent_inner_puzhash = uncurried_nft.nft_state_layer.get_tree_hash()
self.log.debug("Before spend metadata: %s %s \n%s", metadata, singleton_id, disassemble(solution))
for condition in solution.rest().first().rest().as_iter():
self.log.debug("Checking solution condition: %s", disassemble(condition))
if condition.list_len() < 2:
# invalid condition
continue
condition_code = int_from_bytes(condition.first().atom)
self.log.debug("Checking condition code: %r", condition_code)
if condition_code == -24:
# metadata update
# (-24 (meta updater puzzle) url)
metadata_list = list(metadata.as_python())
new_metadata = []
for metadata_entry in metadata_list:
key = metadata_entry[0]
if key == b"u":
new_metadata.append((b"u", [condition.rest().rest().first().atom] + list(metadata_entry[1:])))
else:
new_metadata.append((b"h", metadata_entry[1]))
metadata = Program.to(new_metadata)
elif condition_code == 51 and int_from_bytes(condition.rest().rest().first().atom) == 1:
puzhash = bytes32(condition.rest().first().atom)
self.log.debug("Got back puzhash from solution: %s", puzhash)
derivation_record: Optional[
DerivationRecord
] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(puzhash)
if derivation_record is None:
# we potentially sent it somewhere
await self.remove_coin(coin_spend.coin, in_transaction=in_transaction)
return
new_inner_puzzle = puzzle_for_pk(derivation_record.pubkey)
else:
raise ValueError("Invalid condition")
if new_inner_puzzle is None:
raise ValueError("Invalid puzzle")
parent_coin = None
coin_record = await self.wallet_state_manager.coin_store.get_coin_record(coin_name)
if coin_record is None:
coin_states: Optional[List[CoinState]] = await self.wallet_state_manager.wallet_node.get_coin_state(
[coin_name]
)
if coin_states is not None:
parent_coin = coin_states[0].coin
if coin_record is not None:
parent_coin = coin_record.coin
if parent_coin is None:
raise ValueError("Error in finding parent")
self.log.debug("Got back updated metadata: %s", metadata)
child_puzzle: Program = nft_puzzles.create_full_puzzle(
singleton_id,
metadata,
bytes32(uncurried_nft.metdata_updater_hash.atom),
new_inner_puzzle,
)
self.log.debug(
"Created NFT full puzzle with inner: %s",
nft_puzzles.create_full_puzzle_with_nft_puzzle(singleton_id, uncurried_nft.inner_puzzle),
)
child_coin: Optional[Coin] = None
for new_coin in coin_spend.additions():
self.log.debug(
"Comparing addition: %s with %s, amount: %s ",
new_coin.puzzle_hash,
child_puzzle.get_tree_hash(),
new_coin.amount,
)
if new_coin.puzzle_hash == child_puzzle.get_tree_hash():
child_coin = new_coin
break
else:
raise ValueError("Invalid NFT spend on %r" % coin_name)
self.log.info("Adding a new NFT to wallet: %s", child_coin)
await self.add_coin(
child_coin,
child_puzzle,
LineageProof(parent_coin.parent_coin_info, parent_inner_puzhash, parent_coin.amount),
in_transaction=in_transaction,
)
async def add_coin(self, coin: Coin, puzzle: Program, lineage_proof: LineageProof, in_transaction: bool) -> None:
my_nft_coins = self.nft_wallet_info.my_nft_coins
for coin_info in my_nft_coins:
if coin_info.coin == coin:
my_nft_coins.remove(coin_info)
my_nft_coins.append(NFTCoinInfo(coin, lineage_proof, puzzle))
new_nft_wallet_info = NFTWalletInfo(
my_nft_coins,
self.nft_wallet_info.did_wallet_id,
)
await self.save_info(new_nft_wallet_info, in_transaction=in_transaction)
await self.wallet_state_manager.add_interested_coin_ids([coin.name()], in_transaction=in_transaction)
self.wallet_state_manager.state_changed("nft_coin_added", self.wallet_info.id)
return
async def remove_coin(self, coin: Coin, in_transaction: bool) -> None:
my_nft_coins = self.nft_wallet_info.my_nft_coins
for coin_info in my_nft_coins:
if coin_info.coin == coin:
my_nft_coins.remove(coin_info)
new_nft_wallet_info = NFTWalletInfo(
my_nft_coins,
self.nft_wallet_info.did_wallet_id,
)
await self.save_info(new_nft_wallet_info, in_transaction=in_transaction)
self.wallet_state_manager.state_changed("nft_coin_removed", self.wallet_info.id)
return
def puzzle_for_pk(self, pk: G1Element) -> Program:
if not self.nft_wallet_info.did_wallet_id:
inner_puzzle = self.standard_wallet.puzzle_for_pk(bytes(pk))
else:
raise NotImplementedError
provenance_puzzle = Program.to([NFT_STATE_LAYER_MOD_HASH, inner_puzzle])
self.log.debug(
"Wallet name %s generated a puzzle: %s", self.wallet_info.name, provenance_puzzle.get_tree_hash()
)
return provenance_puzzle
async def generate_new_nft(
self, metadata: Program, target_puzzle_hash: bytes32 = None, fee: uint64 = uint64(0)
) -> Optional[SpendBundle]:
"""
This must be called under the wallet state manager lock
"""
amount = uint64(1)
coins = await self.standard_wallet.select_coins(amount)
if coins is None:
return None
self.log.debug("Attempt to generate a new NFT")
origin = coins.copy().pop()
genesis_launcher_puz = nft_puzzles.LAUNCHER_PUZZLE
launcher_coin = Coin(origin.name(), genesis_launcher_puz.get_tree_hash(), uint64(amount))
self.log.debug("Generating NFT with launcher coin %s and metadata: %s", launcher_coin, metadata)
inner_puzzle = await self.standard_wallet.get_new_puzzle()
# singleton eve
eve_fullpuz = nft_puzzles.create_full_puzzle(
launcher_coin.name(), metadata, NFT_METADATA_UPDATER.get_tree_hash(), inner_puzzle
)
# launcher announcement
announcement_set: Set[Announcement] = set()
announcement_message = Program.to([eve_fullpuz.get_tree_hash(), amount, []]).get_tree_hash()
announcement_set.add(Announcement(launcher_coin.name(), announcement_message))
self.log.debug(
"Creating transaction for launcher: %s and other coins: %s (%s)", origin, coins, announcement_set
)
# store the launcher transaction in the wallet state
tx_record: Optional[TransactionRecord] = await self.standard_wallet.generate_signed_transaction(
uint64(amount),
genesis_launcher_puz.get_tree_hash(),
fee,
origin.name(),
coins,
None,
False,
announcement_set,
)
genesis_launcher_solution = Program.to([eve_fullpuz.get_tree_hash(), amount, []])
# launcher spend to generate the singleton
launcher_cs = CoinSpend(launcher_coin, genesis_launcher_puz, genesis_launcher_solution)
launcher_sb = SpendBundle([launcher_cs], AugSchemeMPL.aggregate([]))
eve_coin = Coin(launcher_coin.name(), eve_fullpuz.get_tree_hash(), uint64(amount))
if tx_record is None or tx_record.spend_bundle is None:
return None
if not target_puzzle_hash:
target_puzzle_hash = inner_puzzle.get_tree_hash()
condition_list = [make_create_coin_condition(target_puzzle_hash, amount, [target_puzzle_hash])]
innersol = solution_for_conditions(condition_list)
# EVE SPEND BELOW
fullsol = Program.to(
[
[launcher_coin.parent_coin_info, launcher_coin.amount],
eve_coin.amount,
Program.to(
[
innersol,
amount,
0,
]
),
]
)
list_of_coinspends = [CoinSpend(eve_coin, eve_fullpuz, fullsol)]
eve_spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([]))
eve_spend_bundle = await self.sign(eve_spend_bundle)
full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend_bundle, launcher_sb])
nft_record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
to_puzzle_hash=eve_fullpuz.get_tree_hash(),
amount=uint64(amount),
fee_amount=fee,
confirmed=False,
sent=uint32(0),
spend_bundle=full_spend,
additions=full_spend.additions(),
removals=full_spend.removals(),
wallet_id=self.wallet_info.id,
sent_to=[],
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32(token_bytes()),
memos=list(compute_memos(full_spend).items()),
)
await self.standard_wallet.push_transaction(nft_record)
return nft_record.spend_bundle
async def sign(self, spend_bundle: SpendBundle) -> SpendBundle:
sigs: List[G2Element] = []
for spend in spend_bundle.coin_spends:
try:
uncurried_nft = UncurriedNFT.uncurry(spend.puzzle_reveal.to_program())
except Exception as e:
# This only happens while you changed the NFT chialisp but didn't update the UncurriedNFT class.
self.log.error(e)
else:
self.log.debug("Found a NFT state layer to sign")
puzzle_hash = uncurried_nft.inner_puzzle.get_tree_hash()
keys = await self.wallet_state_manager.get_keys(puzzle_hash)
assert keys
_, private = keys
synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH)
error, conditions, cost = conditions_dict_for_solution(
spend.puzzle_reveal.to_program(),
spend.solution.to_program(),
self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM,
)
if conditions is not None:
synthetic_pk = synthetic_secret_key.get_g1()
for pk, msg in pkm_pairs_for_conditions_dict(
conditions, spend.coin.name(), self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
):
try:
assert bytes(synthetic_pk) == pk
sigs.append(AugSchemeMPL.sign(synthetic_secret_key, msg))
except AssertionError:
raise ValueError("This spend bundle cannot be signed by the NFT wallet")
agg_sig = AugSchemeMPL.aggregate(sigs)
return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)])
async def _make_nft_transaction(
self, nft_coin_info: NFTCoinInfo, inner_solution: Program, fee: uint64 = uint64(0)
) -> TransactionRecord:
coin = nft_coin_info.coin
amount = coin.amount
full_puzzle = nft_coin_info.full_puzzle
lineage_proof = nft_coin_info.lineage_proof
self.log.debug("Inner solution: %r", disassemble(inner_solution))
full_solution = Program.to(
[
[lineage_proof.parent_name, lineage_proof.inner_puzzle_hash, lineage_proof.amount],
coin.amount,
Program.to(
[
inner_solution,
amount,
0,
]
),
]
)
list_of_coinspends = [CoinSpend(coin, full_puzzle.to_serialized_program(), full_solution)]
spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([]))
spend_bundle = await self.sign(spend_bundle)
full_spend = SpendBundle.aggregate([spend_bundle])
self.log.debug("Memos are: %r", list(compute_memos(full_spend).items()))
nft_record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
to_puzzle_hash=full_puzzle.get_tree_hash(),
amount=uint64(amount),
fee_amount=fee,
confirmed=False,
sent=uint32(0),
spend_bundle=full_spend,
additions=full_spend.additions(),
removals=full_spend.removals(),
wallet_id=self.wallet_info.id,
sent_to=[],
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32(token_bytes()),
memos=list(compute_memos(full_spend).items()),
)
return nft_record
async def update_metadata(
self, nft_coin_info: NFTCoinInfo, uri: str, fee: uint64 = uint64(0)
) -> Optional[SpendBundle]:
coin = nft_coin_info.coin
# we're not changing it
uncurried_nft = UncurriedNFT.uncurry(nft_coin_info.full_puzzle)
puzzle_hash = uncurried_nft.inner_puzzle.get_tree_hash()
condition_list = [make_create_coin_condition(puzzle_hash, coin.amount, [puzzle_hash])]
condition_list.append([int_to_bytes(-24), NFT_METADATA_UPDATER, uri.encode("utf-8")])
self.log.info("Attempting to add a url to NFT coin %s in the metadata: %s", nft_coin_info, uri)
inner_solution = solution_for_conditions(condition_list)
nft_tx_record = await self._make_nft_transaction(nft_coin_info, inner_solution, fee)
await self.standard_wallet.push_transaction(nft_tx_record)
return nft_tx_record.spend_bundle
async def transfer_nft(
self,
nft_coin_info: NFTCoinInfo,
puzzle_hash: bytes32,
did_hash=None,
fee: uint64 = uint64(0),
) -> Optional[SpendBundle]:
self.log.debug("Attempt to transfer a new NFT")
coin = nft_coin_info.coin
self.log.debug("Transfering NFT coin %r to puzhash: %s", nft_coin_info, puzzle_hash)
amount = coin.amount
condition_list = [make_create_coin_condition(puzzle_hash, amount, [bytes32(puzzle_hash)])]
self.log.debug("Condition for new coin: %r", condition_list)
inner_solution = solution_for_conditions(condition_list)
nft_tx_record = await self._make_nft_transaction(nft_coin_info, inner_solution, fee)
await self.standard_wallet.push_transaction(nft_tx_record)
return nft_tx_record.spend_bundle
def get_current_nfts(self) -> List[NFTCoinInfo]:
return self.nft_wallet_info.my_nft_coins
async def save_info(self, nft_info: NFTWalletInfo, in_transaction: bool) -> None:
self.nft_wallet_info = nft_info
current_info = self.wallet_info
data_str = json.dumps(nft_info.to_json_dict())
wallet_info = WalletInfo(current_info.id, current_info.name, current_info.type, data_str)
self.wallet_info = wallet_info
await self.wallet_state_manager.user_store.update_wallet(wallet_info, in_transaction)

View File

@ -0,0 +1,122 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Type, TypeVar
from chia.types.blockchain_format.program import Program
from chia.wallet.puzzles.load_clvm import load_clvm
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
NFT_MOD = load_clvm("nft_state_layer.clvm")
_T_UncurriedNFT = TypeVar("_T_UncurriedNFT", bound="UncurriedNFT")
@dataclass(frozen=True)
class UncurriedNFT:
"""
A simple solution for uncurry NFT puzzle.
Initial the class with a full NFT puzzle, it will do a deep uncurry.
This is the only place you need to change after modified the Chialisp curried parameters.
"""
nft_mod_hash: Program
"""NFT module hash"""
nft_state_layer: Program
"""NFT state layer puzzle"""
singleton_struct: Program
"""
Singleton struct
[singleton_mod_hash, singleton_launcher_id, launcher_puzhash]
"""
singleton_mod_hash: Program
singleton_launcher_id: Program
launcher_puzhash: Program
owner_did: Program
"""Owner's DID"""
metdata_updater_hash: Program
"""Metadata updater puzzle hash"""
transfer_program_hash: Program
"""Puzzle hash of the transfer program"""
transfer_program_curry_params: Program
"""
Curried parameters of the transfer program
[royalty_address, trade_price_percentage, settlement_mod_hash, cat_mod_hash]
"""
royalty_address: Program
trade_price_percentage: Program
settlement_mod_hash: Program
cat_mod_hash: Program
metadata: Program
"""
NFT metadata
[("u", data_uris), ("h", data_hash)]
"""
data_uris: Program
data_hash: Program
inner_puzzle: Program
"""NFT state layer inner puzzle"""
@classmethod
def uncurry(cls: Type[_T_UncurriedNFT], puzzle: Program) -> UncurriedNFT:
"""
Try to uncurry a NFT puzzle
:param cls UncurriedNFT class
:param puzzle: Puzzle program
:return Uncurried NFT
"""
mod, curried_args = puzzle.uncurry()
if mod != SINGLETON_TOP_LAYER_MOD:
raise ValueError(f"Cannot uncurry NFT puzzle, failed on singleton top layer: Mod {mod}")
try:
(singleton_struct, nft_state_layer) = curried_args.as_iter()
singleton_mod_hash = singleton_struct.first()
singleton_launcher_id = singleton_struct.rest().first()
launcher_puzhash = singleton_struct.rest().rest()
except ValueError as e:
raise ValueError(f"Cannot uncurry singleton top layer: Args {curried_args}") from e
mod, curried_args = curried_args.rest().first().uncurry()
if mod != NFT_MOD:
raise ValueError(f"Cannot uncurry NFT puzzle, failed on NFT state layer: Mod {mod}")
try:
# Set nft parameters
(nft_mod_hash, metadata, metdata_updater_hash, inner_puzzle) = curried_args.as_iter()
# Set metadata
for kv_pair in metadata.as_iter():
if kv_pair.first().as_atom() == b"u":
data_uris = kv_pair.rest()
if kv_pair.first().as_atom() == b"h":
data_hash = kv_pair.rest()
except Exception as e:
raise ValueError(f"Cannot uncurry NFT state layer: Args {curried_args}") from e
return cls(
nft_mod_hash=nft_mod_hash,
nft_state_layer=nft_state_layer,
singleton_struct=singleton_struct,
singleton_mod_hash=singleton_mod_hash,
singleton_launcher_id=singleton_launcher_id,
launcher_puzhash=launcher_puzhash,
metadata=metadata,
data_uris=data_uris,
data_hash=data_hash,
metdata_updater_hash=metdata_updater_hash,
inner_puzzle=inner_puzzle,
# TODO Set/Remove following fields after NFT1 implemented
owner_did=Program.to([]),
transfer_program_hash=Program.to([]),
transfer_program_curry_params=Program.to([]),
royalty_address=Program.to([]),
trade_price_percentage=Program.to([]),
settlement_mod_hash=Program.to([]),
cat_mod_hash=Program.to([]),
)

View File

@ -1 +1 @@
ff02ffff01ff02ff5effff04ff02ffff04ffff04ff05ffff04ffff0bff2cff0580ffff04ff0bff80808080ffff04ffff02ff17ff2f80ffff04ff5fffff04ffff02ff2effff04ff02ffff04ff17ff80808080ffff04ffff0bff82027fff82057fff820b7f80ffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff820bffff80808080808080808080808080ffff04ffff01ffffffff81ca3dff46ff0233ffff3c04ff01ff0181cbffffff02ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff2cff3480ffff0bff22ffff0bff22ffff0bff2cff5c80ff0980ffff0bff22ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff0bffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff13ff80808080ff820b9f80ffff01ff02ff26ffff04ff02ffff04ffff02ff13ffff04ff5fffff04ff17ffff04ff2fffff04ff81bfffff04ff82017fffff04ff1bff8080808080808080ffff04ff82017fff8080808080ffff01ff088080ff0180ffff01ff02ffff03ff17ffff01ff02ffff03ffff20ff81bf80ffff0182017fffff01ff088080ff0180ffff01ff088080ff018080ff0180ffff04ffff04ff05ff2780ffff04ffff10ff0bff5780ff778080ff02ffff03ff05ffff01ff02ffff03ffff09ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff01818f80ffff01ff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ffff04ff81b9ff82017980ff808080808080ffff01ff02ff5affff04ff02ffff04ffff02ffff03ffff09ff11ff7880ffff01ff04ff78ffff04ffff02ff36ffff04ff02ffff04ff13ffff04ff29ffff04ffff0bff2cff5b80ffff04ff2bff80808080808080ff398080ffff01ff02ffff03ffff09ff11ff2480ffff01ff04ff24ffff04ffff0bff20ff2980ff398080ffff010980ff018080ff0180ffff04ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff04ffff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ff17ff808080808080ff80808080808080ff0180ffff01ff04ff80ffff04ff80ff17808080ff0180ffffff02ffff03ff05ffff01ff04ff09ffff02ff26ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff0bff22ffff0bff2cff5880ffff0bff22ffff0bff22ffff0bff2cff5c80ff0580ffff0bff22ffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff2cff058080ff0180ffff04ffff04ff28ffff04ff5fff808080ffff02ff7effff04ff02ffff04ffff04ffff04ff2fff0580ffff04ff5fff82017f8080ffff04ffff02ff7affff04ff02ffff04ff0bffff04ff05ffff01ff808080808080ffff04ff17ffff04ff81bfffff04ff82017fffff04ffff0bff8204ffffff02ff36ffff04ff02ffff04ff09ffff04ff820affffff04ffff0bff2cff2d80ffff04ff15ff80808080808080ff8216ff80ffff04ff8205ffffff04ff820bffff808080808080808080808080ff02ff2affff04ff02ffff04ff5fffff04ff3bffff04ffff02ffff03ff17ffff01ff09ff2dffff0bff27ffff02ff36ffff04ff02ffff04ff29ffff04ff57ffff04ffff0bff2cff81b980ffff04ff59ff80808080808080ff81b78080ff8080ff0180ffff04ff17ffff04ff05ffff04ff8202ffffff04ffff04ffff04ff24ffff04ffff0bff7cff2fff82017f80ff808080ffff04ffff04ff30ffff04ffff0bff81bfffff0bff7cff15ffff10ff82017fffff11ff8202dfff2b80ff8202ff808080ff808080ff138080ff80808080808080808080ff018080
ff02ffff01ff02ff5effff04ff02ffff04ffff04ff05ffff04ffff0bff2cff0580ffff04ff0bff80808080ffff04ffff02ff17ff2f80ffff04ff5fffff04ffff02ff2effff04ff02ffff04ff17ff80808080ffff04ffff0bff82027fff82057fff820b7f80ffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff820bffff80808080808080808080808080ffff04ffff01ffffffff81ca3dff46ff0233ffff3c04ff01ff0181cbffffff02ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff2cff3480ffff0bff22ffff0bff22ffff0bff2cff5c80ff0980ffff0bff22ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff0bffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff13ff80808080ff820b9f80ffff01ff02ff26ffff04ff02ffff04ffff02ff13ffff04ff5fffff04ff17ffff04ff2fffff04ff81bfffff04ff82017fffff04ff1bff8080808080808080ffff04ff82017fff8080808080ffff01ff088080ff0180ffff01ff02ffff03ff17ffff01ff02ffff03ffff20ff81bf80ffff0182017fffff01ff088080ff0180ffff01ff088080ff018080ff0180ffff04ffff04ff05ff2780ffff04ffff10ff0bff5780ff778080ff02ffff03ff05ffff01ff02ffff03ffff09ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff01818f80ffff01ff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ffff04ff81b9ff82017980ff808080808080ffff01ff02ff5affff04ff02ffff04ffff02ffff03ffff09ff11ff7880ffff01ff04ff78ffff04ffff02ff36ffff04ff02ffff04ff13ffff04ff29ffff04ffff0bff2cff5b80ffff04ff2bff80808080808080ff398080ffff01ff02ffff03ffff09ff11ff2480ffff01ff04ff24ffff04ffff0bff20ff2980ff398080ffff010980ff018080ff0180ffff04ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff04ffff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ff17ff808080808080ff80808080808080ff0180ffff01ff04ff80ffff04ff80ff17808080ff0180ffffff02ffff03ff05ffff01ff04ff09ffff02ff26ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff0bff22ffff0bff2cff5880ffff0bff22ffff0bff22ffff0bff2cff5c80ff0580ffff0bff22ffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff2cff058080ff0180ffff04ffff04ff28ffff04ff5fff808080ffff02ff7effff04ff02ffff04ffff04ffff04ff2fff0580ffff04ff5fff82017f8080ffff04ffff02ff7affff04ff02ffff04ff0bffff04ff05ffff01ff808080808080ffff04ff17ffff04ff81bfffff04ff82017fffff04ffff0bff8204ffffff02ff36ffff04ff02ffff04ff09ffff04ff820affffff04ffff0bff2cff2d80ffff04ff15ff80808080808080ff8216ff80ffff04ff8205ffffff04ff820bffff808080808080808080808080ff02ff2affff04ff02ffff04ff5fffff04ff3bffff04ffff02ffff03ff17ffff01ff09ff2dffff0bff27ffff02ff36ffff04ff02ffff04ff29ffff04ff57ffff04ffff0bff2cff81b980ffff04ff59ff80808080808080ff81b78080ff8080ff0180ffff04ff17ffff04ff05ffff04ff8202ffffff04ffff04ffff04ff24ffff04ffff0bff7cff2fff82017f80ff808080ffff04ffff04ff30ffff04ffff0bff81bfffff0bff7cff15ffff10ff82017fffff11ff8202dfff2b80ff8202ff808080ff808080ff138080ff80808080808080808080ff018080

View File

@ -5,17 +5,19 @@
(mod
(
MY_PUBKEY ; the public key of the owner used for signing transactions
INNER_PUZZLE ; Standard P2 inner puzzle, used to record the ownership of the DID.
RECOVERY_DID_LIST_HASH ; the list of DIDs that can send messages to you for recovery we store only the hash so that we don't have to reveal every time we make a message spend
NUM_VERIFICATIONS_REQUIRED ; how many of the above list are required for a recovery
Truths ; Truths are sent from the singleton layer
mode ; this indicates which spend mode we want. Create message, recover, or self-destruct
new_amount ; DIDs can receive payments so when we recreate ourselves sometimes we want to change our amount
message ; this is a list of messages when creating a message spend, or a new puzhash when recovering or self destructing
new_inner_puzhash ; this is used during the message creation spend to optionally give ourselves a new inner puzzle - this is useful for updating our recovery list
SINGLETON_STRUCT ; my singleton_struct, formerly a Truth - ((SINGLETON_MOD_HASH, (LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
METADATA ; Customized metadata, e.g KYC info
mode ; this indicates which spend mode we want. 0. Recovery mode 1. Run INNER_PUZZLE with p2_solution
my_amount_or_inner_solution ; In mode 0, we use this to recover our coin and assert it is our actual amount
; In mode 1 this is the solution of the inner P2 puzzle, only required in the create message mode and transfer mode.
new_inner_puzhash ; In recovery mode, this will be the new wallet DID puzzle hash
parent_innerpuzhash_amounts_for_recovery_ids ; during a recovery we need extra information about our recovery list coins
pubkey ; this is the new pubkey used for a recovery
recovery_list_reveal ; this is the reveal of the stored list of DIDs approved for recovery
my_id ; my coin ID
)
;message is the new puzzle in the recovery and standard spend cases
@ -24,7 +26,6 @@
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(include singleton_truths.clib)
; takes a lisp tree and returns the hash of it
(defun sha256tree1 (TREE)
@ -50,7 +51,7 @@
)
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
; return the full puzzlehash for a singleton with the innerpuzzle curried in
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
@ -59,104 +60,76 @@
)
)
(defmacro create_new_coin (amount new_puz)
(qq (c CREATE_COIN (c (unquote new_puz) (c (unquote amount) ()))))
)
; this loops over our identities to check list, and checks if we have been given parent information for this identity
; the reason for this is because we might only require 3/5 of the IDs give approval messages for a recovery
; if we have the information for an identity then we create a consume message using that information
(defun check_messages_from_identities (SINGLETON_STRUCT num_verifications_required identities my_id output new_puz parent_innerpuzhash_amounts_for_recovery_ids pubkey num_verifications)
(defun check_messages_from_identities (SINGLETON_STRUCT num_verifications_required identities my_id new_puz parent_innerpuzhash_amounts_for_recovery_ids pubkey num_verifications)
(if identities
(if (f parent_innerpuzhash_amounts_for_recovery_ids)
; if we have parent information then we should create a consume coin condition
(check_messages_from_identities
SINGLETON_STRUCT
num_verifications_required
(r identities)
my_id
(c
(create_consume_message
; create coin_id from DID
(create_coin_ID_for_recovery
SINGLETON_STRUCT
(f identities)
(f (f parent_innerpuzhash_amounts_for_recovery_ids))
(f (r (f parent_innerpuzhash_amounts_for_recovery_ids)))
(f (r (r (f parent_innerpuzhash_amounts_for_recovery_ids)))))
my_id
new_puz
pubkey)
output)
new_puz
(r parent_innerpuzhash_amounts_for_recovery_ids)
pubkey
(+ num_verifications 1)
(c
(create_consume_message
; create coin_id from DID
(create_coin_ID_for_recovery
SINGLETON_STRUCT
(f identities)
(f (f parent_innerpuzhash_amounts_for_recovery_ids))
(f (r (f parent_innerpuzhash_amounts_for_recovery_ids)))
(f (r (r (f parent_innerpuzhash_amounts_for_recovery_ids)))))
my_id
new_puz
pubkey
)
(check_messages_from_identities
SINGLETON_STRUCT
num_verifications_required
(r identities)
my_id
new_puz
(r parent_innerpuzhash_amounts_for_recovery_ids)
pubkey
(+ num_verifications 1)
)
)
; if no parent information found for this identity, move on to next in list
(check_messages_from_identities
SINGLETON_STRUCT
(r identities)
my_id
output
new_puz
(r parent_innerpuzhash_amounts_for_recovery_ids)
pubkey
num_verifications
)
)
;if we're out of identites to check for, return our output
(if (> num_verifications num_verifications_required)
(c (list AGG_SIG_UNSAFE pubkey new_puz) output)
(if (= num_verifications num_verifications_required)
(c (list AGG_SIG_UNSAFE pubkey new_puz) output)
(x)
)
;if we're out of identites to check for, check we have enough
(if (> num_verifications (- num_verifications_required 1))
(list (list AGG_SIG_UNSAFE pubkey new_puz) )
(x)
)
)
)
; for a list of messages in the format (type . message) create a message
; type 0 is 0 value coin
; type 1 is coin announcement
; type 2 is puzzle announcement
(defun create_messages (messages)
(if messages
(c
(if (f (f messages))
(list (if (= (f (f messages)) 1) CREATE_COIN_ANNOUNCEMENT CREATE_PUZZLE_ANNOUNCEMENT) (r (f messages)))
(list CREATE_COIN (r (f messages)) 0)
)
(create_messages (r messages))
)
()
)
)
;Spend modes:
;0 = exit spend
;1 = create messages and recreate singleton
;2 = recovery
;0 = recovery
;1 = run the INNER_PUZZLE
;MAIN
(if mode
(if (= mode 1)
; mode one - create messages and recreate singleton
(c (list CREATE_COIN new_inner_puzhash new_amount) (c (list AGG_SIG_ME MY_PUBKEY (sha256tree1 (list new_inner_puzhash new_amount message))) (create_messages message)))
; mode two - recovery
; check that recovery list is not empty
(if recovery_list_reveal
(if (= (sha256tree1 recovery_list_reveal) RECOVERY_DID_LIST_HASH)
(check_messages_from_identities (singleton_struct_truth Truths) NUM_VERIFICATIONS_REQUIRED recovery_list_reveal (my_id_truth Truths) (list (create_new_coin new_amount message)) message parent_innerpuzhash_amounts_for_recovery_ids pubkey 0)
(x)
)
(x)
)
)
; mode zero - exit spend
(list (list CREATE_COIN 0x00 -113) (list CREATE_COIN message new_amount) (list AGG_SIG_ME MY_PUBKEY (sha256tree1 (list new_amount message))))
)
; mode 1 - run INNER_PUZZLE
(a INNER_PUZZLE my_amount_or_inner_solution)
; mode 0 - recovery
(if (all (= (sha256tree1 recovery_list_reveal) RECOVERY_DID_LIST_HASH) (> NUM_VERIFICATIONS_REQUIRED 0))
(c (list ASSERT_MY_AMOUNT my_amount_or_inner_solution)
(c (list CREATE_COIN new_inner_puzhash my_amount_or_inner_solution (list new_inner_puzhash))
(c (list ASSERT_MY_COIN_ID my_id)
(check_messages_from_identities SINGLETON_STRUCT NUM_VERIFICATIONS_REQUIRED recovery_list_reveal my_id new_inner_puzhash parent_innerpuzhash_amounts_for_recovery_ids pubkey 0)
)
)
)
(x)
)
)
)

View File

@ -1 +1 @@
ff02ffff01ff02ffff03ff5fffff01ff02ffff03ffff09ff5fffff010180ffff01ff04ffff04ff24ffff04ff8202ffffff04ff81bfff80808080ffff04ffff04ff20ffff04ff05ffff04ffff02ff7effff04ff02ffff04ffff04ff8202ffffff04ff81bfffff04ff82017fff80808080ff80808080ff80808080ffff02ff36ffff04ff02ffff04ff82017fff808080808080ffff01ff02ffff03ff8217ffffff01ff02ffff03ffff09ffff02ff7effff04ff02ffff04ff8217ffff80808080ff0b80ffff01ff02ff3affff04ff02ffff04ff8201efffff04ff17ffff04ff8217ffffff04ff818fffff04ffff04ffff04ff24ffff04ff82017fffff04ff81bfff80808080ff8080ffff04ff82017fffff04ff8205ffffff04ff820bffffff01ff808080808080808080808080ffff01ff088080ff0180ffff01ff088080ff018080ff0180ffff01ff04ffff04ff24ffff01ff00ff818f8080ffff04ffff04ff24ffff04ff82017fffff04ff81bfff80808080ffff04ffff04ff20ffff04ff05ffff04ffff02ff7effff04ff02ffff04ffff04ff81bfffff04ff82017fff808080ff80808080ff80808080ff8080808080ff0180ffff04ffff01ffffffff3231ff3d02ffff333cff3eff0401ffffff0102ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff32ffff0bff7cff5c80ffff0bff32ffff0bff32ffff0bff7cff2280ff0980ffff0bff32ff0bffff0bff7cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ff82027fffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fffff04ffff04ffff04ff28ffff04ffff0bffff0bffff02ff26ffff04ff02ffff04ff05ffff04ff27ffff04ff82047fffff04ff820a7fffff04ff82167fff8080808080808080ffff02ff7effff04ff02ffff04ffff02ff2effff04ff02ffff04ff2fffff04ff81bfffff04ff8202ffff808080808080ff8080808080ff2f80ff808080ff5f80ffff04ff81bfffff04ff82037fffff04ff8202ffffff04ffff10ff8205ffffff010180ff808080808080808080808080ffff01ff02ff3affff04ff02ffff04ff05ffff04ff37ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff04ff8202ffffff04ff8205ffff808080808080808080808080ff0180ffff01ff02ffff03ffff15ff8205ffff0b80ffff01ff04ffff04ff30ffff04ff8202ffffff04ff81bfff80808080ff5f80ffff01ff02ffff03ffff09ff8205ffff0b80ffff01ff04ffff04ff30ffff04ff8202ffffff04ff81bfff80808080ff5f80ffff01ff088080ff018080ff018080ff0180ffffff0bff17ffff02ff5effff04ff02ffff04ff09ffff04ff2fffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff0bff1d8080ff80808080ff808080808080ff5f80ff02ffff03ff05ffff01ff04ffff02ffff03ff11ffff01ff04ffff02ffff03ffff09ff11ffff010180ffff0134ffff012c80ff0180ffff04ff19ff808080ffff01ff04ff24ffff04ff19ffff01ff8080808080ff0180ffff02ff36ffff04ff02ffff04ff0dff8080808080ff8080ff0180ffff04ffff0101ffff04ffff04ff34ffff04ff05ff808080ffff04ffff04ff30ffff04ff17ffff04ff0bff80808080ff80808080ffff0bff32ffff0bff7cff3880ffff0bff32ffff0bff32ffff0bff7cff2280ff0580ffff0bff32ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff7cff7c80ff8080808080ffff0bff7cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
ff02ffff01ff02ffff03ff81bfffff01ff02ff05ff82017f80ffff01ff02ffff03ffff22ffff09ffff02ff7effff04ff02ffff04ff8217ffff80808080ff0b80ffff15ff17ff808080ffff01ff04ffff04ff28ffff04ff82017fff808080ffff04ffff04ff34ffff04ff8202ffffff04ff82017fffff04ffff04ff8202ffff8080ff8080808080ffff04ffff04ff38ffff04ff822fffff808080ffff02ff26ffff04ff02ffff04ff2fffff04ff17ffff04ff8217ffffff04ff822fffffff04ff8202ffffff04ff8205ffffff04ff820bffffff01ff8080808080808080808080808080ffff01ff088080ff018080ff0180ffff04ffff01ffffffff313dff4946ffff0233ff3c04ffffff0101ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff22ff3c80ffff0bff2affff0bff2affff0bff22ff3280ff0980ffff0bff2aff0bffff0bff22ff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff17ffff01ff02ffff03ff82013fffff01ff04ffff04ff30ffff04ffff0bffff0bffff02ff36ffff04ff02ffff04ff05ffff04ff27ffff04ff82023fffff04ff82053fffff04ff820b3fff8080808080808080ffff02ff7effff04ff02ffff04ffff02ff2effff04ff02ffff04ff2fffff04ff5fffff04ff82017fff808080808080ff8080808080ff2f80ff808080ffff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fffff04ff5fffff04ff8201bfffff04ff82017fffff04ffff10ff8202ffffff010180ff808080808080808080808080ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff37ffff04ff2fffff04ff5fffff04ff8201bfffff04ff82017fffff04ff8202ffff8080808080808080808080ff0180ffff01ff02ffff03ffff15ff8202ffffff11ff0bffff01018080ffff01ff04ffff04ff20ffff04ff82017fffff04ff5fff80808080ff8080ffff01ff088080ff018080ff0180ff0bff17ffff02ff5effff04ff02ffff04ff09ffff04ff2fffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff0bff1d8080ff80808080ff808080808080ff5f80ffff04ffff0101ffff04ffff04ff2cffff04ff05ff808080ffff04ffff04ff20ffff04ff17ffff04ff0bff80808080ff80808080ffff0bff2affff0bff22ff2480ffff0bff2affff0bff2affff0bff22ff3280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff22ff2280ff8080808080ffff0bff22ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080

View File

@ -1 +1 @@
ef41902d9964f6050f87de98b5c4e34512b7d2abded3fe700f7850ff20323bf2
33143d2bef64f14036742673afd158126b94284b4530a28c354fac202b0c910e

View File

@ -0,0 +1,25 @@
(
(defun search_list_then_resume (key json_object result)
(if result
result
(search_for_value_given_key key (r json_object))
)
)
(defun search_for_value_given_key (key json_object)
(if json_object
(if (l (f from_json_dict)) ; if we are hitting an unlabelled item (atom), just move on
(if (= (f (f json_object)) key) ; if we have found our key
(r (f json_object)) ; return the value
(if (l (r (f json_object))) ; otherwise check if we have hit another dictionary
(search_list_then_resume key json_object (search_for_value_given_key key (r (f json_object)))) ; check new dictionary
(search_for_value_given_key key (r json_object))
)
)
(search_for_value_given_key key (r json_object))
)
()
)
)
)

View File

@ -0,0 +1,85 @@
(mod (
NFT_MOD_HASH
SINGLETON_STRUCT ; ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
CURRENT_OWNER_DID
TRANSFER_PROGRAM_MOD_HASH
TRANSFER_PROGRAM_CURRY_PARAMS
METADATA
my_did_inner_hash
new_did
new_did_inner_hash
transfer_program_reveal
transfer_program_solution
)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(include singleton_truths.clib)
; takes a lisp tree and returns the hash of it
(defun sha256tree1 (TREE)
(if (l TREE)
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
(sha256 ONE TREE)
)
)
(defun-inline nft_puzzle_hash (NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH next_owner_did transfer_program_curry_params metadata)
(puzzle-hash-of-curried-function NFT_MOD_HASH
(sha256tree1 metadata)
(sha256tree1 transfer_program_curry_params)
(sha256 ONE TRANSFER_PROGRAM_MOD_HASH)
(sha256 ONE next_owner_did)
(sha256tree1 SINGLETON_STRUCT)
(sha256 ONE NFT_MOD_HASH)
)
)
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
inner_puzzle_hash
(sha256tree1 SINGLETON_STRUCT)
)
)
; if we find an update, recreate ourselves with the update
; otherwise recreate ourselves as we are
(defun parse_transfer_prog_output (NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did output found_update)
(if output
(if (= (f (f output)) -22)
(c
(list CREATE_COIN (nft_puzzle_hash NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH new_did (f (r (r (f output)))) (f (r (f output)))) ONE (list new_did))
(parse_transfer_prog_output NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did (r output) 1)
)
(c (f output) (parse_transfer_prog_output NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did (r output) found_update))
)
(if found_update ; if we're at the end of the loop, check if we've already recreated ourselves or not
()
(list (list CREATE_COIN (nft_puzzle_hash NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH new_did TRANSFER_PROGRAM_CURRY_PARAMS METADATA) ONE (list new_did)))
)
)
)
; main
(c
(list ASSERT_MY_AMOUNT ONE)
(if new_did
(if (= (sha256tree1 transfer_program_reveal) TRANSFER_PROGRAM_MOD_HASH)
(c
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c CURRENT_OWNER_DID (r (r SINGLETON_STRUCT)))) my_did_inner_hash) (sha256tree1 (list transfer_program_solution new_did))))
; (METADATA CURRY_PARAMS SINGLETON_STRUCT current_owner trade_price_list my_did_inner_hash new_did new_did_inner_hash my_nft_id solution)
(parse_transfer_prog_output NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did (a transfer_program_reveal (list METADATA TRANSFER_PROGRAM_CURRY_PARAMS SINGLETON_STRUCT CURRENT_OWNER_DID my_did_inner_hash new_did new_did_inner_hash transfer_program_solution)) 0)
)
(x)
)
(list
(list CREATE_COIN (nft_puzzle_hash NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH CURRENT_OWNER_DID TRANSFER_PROGRAM_CURRY_PARAMS METADATA) ONE (list CURRENT_OWNER_DID))
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c CURRENT_OWNER_DID (r (r SINGLETON_STRUCT)))) my_did_inner_hash) 'a'))
(list CREATE_PUZZLE_ANNOUNCEMENT CURRENT_OWNER_DID)
)
)
)
)

View File

@ -0,0 +1 @@
ff02ffff01ff04ffff04ff10ffff04ff12ff808080ffff02ffff03ff8202ffffff01ff02ffff03ffff09ffff02ff3effff04ff02ffff04ff820bffff80808080ff2f80ffff01ff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff02ff3effff04ff02ffff04ffff04ff8217ffffff04ff8202ffff808080ff8080808080ff808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff2fffff04ff5fffff04ff81bfffff04ff8202ffffff04ffff02ff820bffffff04ff81bfffff04ff5fffff04ff0bffff04ff17ffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff8217ffff80808080808080808080ffff01ff808080808080808080808080ffff01ff088080ff0180ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff81bfff80808080ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff0bff12ff2f80ffff04ffff0bff12ff1780ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff17ff8080ff8080808080ffff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff016180ff808080ffff04ffff04ff2cffff04ff17ff808080ff8080808080ff018080ffff04ffff01ffffff49ff3f02ff33ff3e04ffff01ff0102ffffff02ffff03ff05ffff01ff02ff26ffff04ff02ffff04ff0dffff04ffff0bff3affff0bff12ff3c80ffff0bff3affff0bff3affff0bff12ff2a80ff0980ffff0bff3aff0bffff0bff12ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff82017fffff01ff02ffff03ffff09ff82047fffff0181ea80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff820a7fff80808080ffff04ffff02ff3effff04ff02ffff04ff82167fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff01ff018080808080808080808080ffff01ff04ff82027fffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff04ff8202ffff80808080808080808080808080ff0180ffff01ff02ffff03ff8202ffff80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff02ff3effff04ff02ffff04ff2fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ff808080ff018080ff0180ffff0bff3affff0bff12ff3880ffff0bff3affff0bff3affff0bff12ff2a80ff0580ffff0bff3affff02ff26ffff04ff02ffff04ff07ffff04ffff0bff12ff1280ff8080808080ffff0bff12ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bff12ff058080ff0180ff018080

View File

@ -0,0 +1 @@
ae8ee2bf9e0bcc189d4d8efa3c8281235981abd75ade592ba71930aa6881f820

View File

@ -0,0 +1,20 @@
(mod (CURRENT_METADATA solution)
; once we find 'u' we don't need to continue looping
(defun add_url (METADATA new_url)
(if METADATA
(if (= (f (f METADATA)) 'u')
(c (c 'u' (c new_url (r (f METADATA)))) (r METADATA))
(c (f METADATA) (add_url (r METADATA) new_url))
)
()
)
)
; main
; returns (new_metadata conditions)
(if solution
(list (add_url CURRENT_METADATA solution) 0)
(list CURRENT_METADATA 0)
)
)

View File

@ -0,0 +1 @@
ff02ffff01ff02ffff03ff0bffff01ff04ffff02ff02ffff04ff02ffff04ff05ffff04ff0bff8080808080ffff01ff808080ffff01ff04ff05ffff01ff80808080ff0180ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080

View File

@ -0,0 +1 @@
3df9de54667a96f32eba322635f14d3474edadf23f396ba5a3e2e077a89a682a

View File

@ -0,0 +1,21 @@
(mod (CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH solution)
; METADATA and METADATA_UPDATER_PUZZLE_HASH are passed in as truths from the layer above
; This program returns ((new_metadata new_metadata_updater_puzhash) conditions)
; once we find 'u' we don't need to continue looping
(defun add_url (METADATA new_url)
(if METADATA
(if (= (f (f METADATA)) 'u')
(c (c 'u' (c new_url (r (f METADATA)))) (r METADATA))
(c (f METADATA) (add_url (r METADATA) new_url))
)
()
)
)
; main
; returns ((new_metadata new_metadata_updater_puzhash) conditions)
(list (list (if solution (add_url CURRENT_METADATA solution) CURRENT_METADATA) METADATA_UPDATER_PUZZLE_HASH) 0)
)

View File

@ -0,0 +1 @@
ff02ffff01ff04ffff04ffff02ffff03ff17ffff01ff02ff02ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff010580ff0180ffff04ff0bff808080ffff01ff808080ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080

View File

@ -0,0 +1 @@
81970d352e6a39a241eaf8ca510a0e669e40d778ba612621c60a50ef6cf29c7b

View File

@ -0,0 +1,83 @@
(mod
(
; SINGLETON_STRUCT is ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
SINGLETON_STRUCT
; CURRY_PARAMS are: (ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH)
CURRY_PARAMS
; DID of the current owner
CURRENT_OWNER
; Royalty program puzzle hash
ROYALTY_PROG_PUZHASH
; DID inner puzzle hash of the current owner
current_did_puzhash
; New NFT owner's DID
new_owner
; DID inner puzzle hash of the new owner
new_did_puzhash
; trade_prices_list is a list of (amount type) pairs
; type 0 is standard chia
; any other type is a bytes32 mod hash of a launcher
trade_prices_list
)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(include sha256tree.clib)
(defconstant CHANGE_OWNERSHIP -22)
(defconstant TEN_THOUSAND 10000)
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
inner_puzzle_hash
(sha256tree SINGLETON_STRUCT)
)
)
; Given a singleton ID, generate the singleton struct
(defun-inline get_singleton_struct (SINGLETON_STRUCT singleton_id)
(c (f SINGLETON_STRUCT) (c singleton_id (r (r SINGLETON_STRUCT))))
)
(defun-inline cat_settlement_puzzle_hash (CAT_MOD_HASH tail_hash SETTLEMENT_MOD_HASH)
(puzzle-hash-of-curried-function CAT_MOD_HASH
SETTLEMENT_MOD_HASH
(sha256 1 tail_hash)
(sha256 1 CAT_MOD_HASH)
)
)
(defun round_down_to_even (value)
(if (logand value 1) (- value 1) value)
)
(defun-inline calculate_percentage (amount percentage)
(f (divmod (* amount percentage) TEN_THOUSAND))
)
; Loop of the trade prices list and either assert a puzzle announcement or generate xch
(defun parse_trade_prices_list ((ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) trade_prices_list my_nft_id)
(if trade_prices_list
(c
(if (r (f trade_prices_list))
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (cat_settlement_puzzle_hash CAT_MOD_HASH (f (r (f trade_prices_list))) SETTLEMENT_MOD_HASH) (sha256tree (c my_nft_id (list (list ROYALTY_ADDRESS (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))))))
(list CREATE_COIN ROYALTY_ADDRESS (round_down_to_even (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))
)
(parse_trade_prices_list (list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) (r trade_prices_list) my_nft_id)
)
()
)
)
; main
(list
(c (list CHANGE_OWNERSHIP new_owner)
(c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (get_singleton_struct SINGLETON_STRUCT CURRENT_OWNER) current_did_puzhash) (f (r SINGLETON_STRUCT)) (sha256tree royalty_prog_solution) new_owner))
(c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (get_singleton_struct SINGLETON_STRUCT new_owner) new_did_puzhash) (f (r SINGLETON_STRUCT)) (sha256tree royalty_prog_solution)))
(parse_trade_prices_list CURRY_PARAMS trade_prices_list (f (r SINGLETON_STRUCT)))
)
)
)
)
)

View File

@ -0,0 +1 @@
ff02ffff01ff04ffff04ffff04ff38ffff04ff81bfff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff5fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff17ff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e808080ff81bf80ff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff81bfff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e80808080ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff8202ffffff04ff15ff808080808080808080ff8080ffff04ffff01ffffff3fff0281eaffff3304ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff3480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff5dffff04ff2dffff04ffff0bffff0101ff5380ffff04ffff0bffff0101ff5d80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff24ffff04ff09ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080

View File

@ -0,0 +1 @@
03557552e0bf8b200d8be8089f54786195ab8c07e3e07c39641f2f4f1fc2e48e

View File

@ -0,0 +1,99 @@
(mod (
NFT_STATE_LAYER_MOD_HASH
METADATA
METADATA_UPDATER_PUZZLE_HASH
INNER_PUZZLE
solution ; either to inner puzzle or metadata updater
my_amount
)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(defun sha256tree1
(TREE)
(if (l TREE)
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
(sha256 1 TREE)
)
)
(defun-inline nft_state_layer_puzzle_hash (NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH inner_puzzle_hash)
(puzzle-hash-of-curried-function NFT_STATE_LAYER_MOD_HASH
inner_puzzle_hash
(sha256 ONE METADATA_UPDATER_PUZZLE_HASH)
(sha256tree1 METADATA)
(sha256 ONE NFT_STATE_LAYER_MOD_HASH)
)
)
(defun wrap_odd_create_coins (NFT_STATE_LAYER_MOD_HASH ((METADATA METADATA_UPDATER_PUZZLE_HASH) conditions) my_amount new_create_coin_condition)
(if conditions
(if (= (f (f conditions)) CREATE_COIN)
(if (logand (f (r (r (f conditions)))) ONE)
(wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount (f conditions))
(c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount new_create_coin_condition))
)
(if (> (f (f conditions)) 0)
(c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount new_create_coin_condition))
(wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount new_create_coin_condition)
)
)
(if new_create_coin_condition
(list (c CREATE_COIN
(c (nft_state_layer_puzzle_hash
NFT_STATE_LAYER_MOD_HASH METADATA
METADATA_UPDATER_PUZZLE_HASH
(f (r new_create_coin_condition)))
(c my_amount
(r (r (r new_create_coin_condition)))
)
)
))
(x)
)
)
)
; take two lists and merge them into one
(defun merge_list (list_a list_b)
(if list_a
(c (f list_a) (merge_list (r list_a) list_b))
list_b
)
)
(defun check_for_metadata_prog_reveal (METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions_loop)
(if conditions_loop
(if (= (f (f conditions_loop)) -24)
(if (= (sha256tree1 (f (r (f conditions_loop)))) METADATA_UPDATER_PUZZLE_HASH)
(a (f (r (f conditions_loop))) (list CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (r (f conditions_loop))))))
(x)
)
(check_for_metadata_prog_reveal METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA (r conditions_loop))
)
(list (list CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH) 0)
)
)
(defun process_metadata_updater (metadata_result conditions)
(c (f metadata_result) (list (merge_list (f (r metadata_result)) conditions)))
)
(defun metadata_updater_loader (METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions)
(process_metadata_updater (check_for_metadata_prog_reveal METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions) conditions)
)
; main
(c
(list ASSERT_MY_AMOUNT my_amount)
(wrap_odd_create_coins
NFT_STATE_LAYER_MOD_HASH
(metadata_updater_loader
METADATA_UPDATER_PUZZLE_HASH METADATA (a INNER_PUZZLE solution)
)
my_amount
0
)
)
)

View File

@ -0,0 +1 @@
ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfffff01ff8080808080808080ffff04ffff01ffffff49ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ffff0181e880ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff05ffff04ff820167ff8080808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ffff04ff0bffff04ff05ff808080ffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff2bffff01ff02ffff03ffff09ff818bff3880ffff01ff02ffff03ffff18ff8202cbff3480ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff4bff80808080808080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff808080808080808080ff0180ffff01ff02ffff03ffff15ff818bff8080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff8080808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff8080808080808080ff018080ff0180ffff01ff02ffff03ff2fffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff81afffff04ffff0bff34ff5380ffff04ffff02ff2effff04ff02ffff04ff23ff80808080ffff04ffff0bff34ff0580ff8080808080808080ffff04ff17ff8201ef808080ff8080ffff01ff088080ff018080ff0180ff018080

View File

@ -0,0 +1 @@
dd8135d546e291df295b376aa89fc409c8c50d7f655d1ff4e845637901bc2f8f

View File

@ -0,0 +1,139 @@
(mod (METADATA CURRY_PARAMS SINGLETON_STRUCT current_owner my_did_inner_hash new_did new_did_inner_hash solution)
; METADATA and CURRY_PARAMS are not actually curried into the mod hash but are curried into the NFT layer and passed in
; This effectively achieves the same result - fixing the values - but means that the transfer program mod never changes
; CURRY_PARAMS are: (ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH)
; SINGLETON_STRUCT is ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
; For this transfer program solution is (trade_prices_list new_url)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(defconstant TEN_THOUSAND 10000)
; trade_prices_list is a list of (amount type) pairs
; type 0 is standard chia
; any other type is a bytes32 mod hash of a launcher
(defun-inline cat_settlement_puzzle_hash (CAT_MOD_HASH tail_hash SETTLEMENT_MOD_HASH)
(puzzle-hash-of-curried-function CAT_MOD_HASH
SETTLEMENT_MOD_HASH
(sha256 ONE tail_hash)
(sha256 ONE CAT_MOD_HASH)
)
)
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
inner_puzzle_hash
(sha256tree1 SINGLETON_STRUCT)
)
)
(defun round_down_to_even (value)
(if (logand value ONE) (- value ONE) value)
)
(defun-inline calculate_percentage (amount percentage)
(f (divmod (* amount percentage) TEN_THOUSAND))
)
(defun sha256tree1 (TREE)
(if (l TREE)
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
(sha256 ONE TREE)
)
)
; Loop of the trade prices list and either assert a puzzle announcement or generate xch
(defun parse_trade_prices_list ((ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) trade_prices_list my_nft_id)
(if trade_prices_list
(c
(if (r (f trade_prices_list))
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (cat_settlement_puzzle_hash CAT_MOD_HASH (f (r (f trade_prices_list))) SETTLEMENT_MOD_HASH) (sha256tree1 (c my_nft_id (list (list ROYALTY_ADDRESS (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))))))
(list CREATE_COIN ROYALTY_ADDRESS (round_down_to_even (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))
)
(parse_trade_prices_list (list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) (r trade_prices_list) my_nft_id)
)
()
)
)
; if we are part of a complex trade then we assert an announcement from the recipient of the trade_price and then parse the trade price list
(defun generate_conditions_for_trade (
SINGLETON_STRUCT
CURRENT_OWNER_DID
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
trade_prices_list
trade_prices_list_hash
)
(c (list CREATE_PUZZLE_ANNOUNCEMENT trade_prices_list_hash)
(c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c new_did (r (r SINGLETON_STRUCT)))) new_did_inner_hash) trade_prices_list_hash (f (r SINGLETON_STRUCT))))
(parse_trade_prices_list CURRY_PARAMS trade_prices_list (f (r SINGLETON_STRUCT)))
)
)
)
; once we find 'u' we don't need to continue looping
(defun add_url (METADATA new_url)
(if METADATA
(if (= (f (f METADATA)) 'u')
(c (c 'u' (c new_url (r (f METADATA)))) (r METADATA))
(c (f METADATA) (add_url (r METADATA) new_url))
)
()
)
)
(defun check_if_add_url (METADATA CURRY_PARAMS solution output)
(if solution
(c (list -22 (add_url METADATA solution) CURRY_PARAMS) output)
output
)
)
(defun check_if_gift (
SINGLETON_STRUCT
CURRENT_OWNER_DID
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
trade_prices_list
)
(if trade_prices_list
(generate_conditions_for_trade
SINGLETON_STRUCT
CURRENT_OWNER_DID
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
trade_prices_list
(sha256tree1 trade_prices_list)
)
(list (list CREATE_PUZZLE_ANNOUNCEMENT 0))
)
)
; main
(check_if_add_url
METADATA
CURRY_PARAMS
(f (r solution))
(check_if_gift
SINGLETON_STRUCT
current_owner
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
(f solution)
)
)
)

View File

@ -0,0 +1 @@
ff02ffff01ff02ff2affff04ff02ffff04ff05ffff04ff0bffff04ff820affffff04ffff02ff3affff04ff02ffff04ff17ffff04ff2fffff04ff0bffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffff80808080808080808080ff80808080808080ffff04ffff01ffffffff3f02ff333effff0401ff01ff82271002ffffffff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff22ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff7cffff0bff34ff2480ffff0bff7cffff0bff7cffff0bff34ff2c80ff0980ffff0bff7cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff04ffff04ffff0181eaffff04ffff02ff22ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff04ff0bff80808080ff2f80ffff012f80ff0180ff02ffff03ff82017fffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ffff02ff7effff04ff02ffff04ff82017fff80808080ff8080808080808080808080ffff01ff04ffff04ff38ffff01ff808080ff808080ff0180ffffff04ffff04ff38ffff04ff8202ffff808080ffff04ffff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff81bfffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff5fff1d8080ff80808080ff808080808080ff8202ffff1580ff808080ffff02ff36ffff04ff02ffff04ff17ffff04ff82017fffff04ff15ff8080808080808080ff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff5dffff04ff2dffff04ffff0bff34ff5380ffff04ffff0bff34ff5d80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff28ffff04ff09ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ffff0bff7cffff0bff34ff3080ffff0bff7cffff0bff7cffff0bff34ff2c80ff0580ffff0bff7cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080

View File

@ -0,0 +1 @@
519b806c31cce5280fa37695149521a301f13f5696d1fd1eeee1856c2d14965c

View File

@ -10,7 +10,7 @@
; synthetic_key_offset: a private key cryptographically generated using the hidden
; puzzle and as inputs `original_public_key`
;
; synthetic_public_key: the public key that is the sum of `original_public_key` and the
; SYNTHETIC_PUBLIC_KEY: the public key that is the sum of `original_public_key` and the
; public key corresponding to `synthetic_key_offset`
;
; original_public_key: a public key, where knowledge of the corresponding private key
@ -23,17 +23,17 @@
(mod
; A puzzle should commit to `synthetic_public_key`
; A puzzle should commit to `SYNTHETIC_PUBLIC_KEY`
;
; The solution should pass in 0 for `original_public_key` if it wants to use
; an arbitrary `delegated_puzzle` (and `solution`) signed by the
; `synthetic_public_key` (whose corresponding private key can be calculated
; `SYNTHETIC_PUBLIC_KEY` (whose corresponding private key can be calculated
; if you know the private key for `original_public_key`)
;
; Or you can solve the hidden puzzle by revealing the `original_public_key`,
; the hidden puzzle in `delegated_puzzle`, and a solution to the hidden puzzle.
(synthetic_public_key original_public_key delegated_puzzle solution)
(SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle solution)
; "assert" is a macro that wraps repeated instances of "if"
; usage: (assert A0 A1 ... An R)
@ -61,9 +61,9 @@
; "is_hidden_puzzle_correct" returns true iff the hidden puzzle is correctly encoded
(defun-inline is_hidden_puzzle_correct (synthetic_public_key original_public_key delegated_puzzle)
(defun-inline is_hidden_puzzle_correct (SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle)
(=
synthetic_public_key
SYNTHETIC_PUBLIC_KEY
(point_add
original_public_key
(pubkey_for_exp (sha256 original_public_key (sha256tree1 delegated_puzzle)))
@ -73,19 +73,19 @@
; "possibly_prepend_aggsig" is the main entry point
(defun-inline possibly_prepend_aggsig (synthetic_public_key original_public_key delegated_puzzle conditions)
(defun-inline possibly_prepend_aggsig (SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle conditions)
(if original_public_key
(assert
(is_hidden_puzzle_correct synthetic_public_key original_public_key delegated_puzzle)
(is_hidden_puzzle_correct SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle)
conditions
)
(c (list AGG_SIG_ME synthetic_public_key (sha256tree1 delegated_puzzle)) conditions)
(c (list AGG_SIG_ME SYNTHETIC_PUBLIC_KEY (sha256tree1 delegated_puzzle)) conditions)
)
)
; main entry point
(possibly_prepend_aggsig
synthetic_public_key original_public_key delegated_puzzle
SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle
(a delegated_puzzle solution))
)

View File

@ -1 +1 @@
ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080

View File

@ -0,0 +1,11 @@
(
;; hash a tree
;; This is used to calculate a puzzle hash given a puzzle program.
(defun sha256tree
(TREE)
(if (l TREE)
(sha256 2 (sha256tree (f TREE)) (sha256tree (r TREE)))
(sha256 1 TREE)
)
)
)

View File

@ -0,0 +1,124 @@
(mod (SINGLETON_STRUCT INNER_PUZZLE lineage_proof my_amount inner_solution)
;; SINGLETON_STRUCT = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
; SINGLETON_STRUCT, INNER_PUZZLE are curried in by the wallet
; EXAMPLE SOLUTION '(0xfadeddab 0xdeadbeef 1 (0xdeadbeef 200) 50 ((51 0xfadeddab 100) (60 "trash") (51 deadbeef 0)))'
; This puzzle is a wrapper around an inner smart puzzle which guarantees uniqueness.
; It takes its singleton identity from a coin with a launcher puzzle which guarantees that it is unique.
(include condition_codes.clvm)
(include curry-and-treehash.clinc) ; also imports the constant ONE == 1
(include singleton_truths.clib)
(defmacro assert items
(if (r items)
(list if (f items) (c assert (r items)) (q . (x)))
(f items)
)
)
(defmacro and ARGS
(if ARGS
(qq (if (unquote (f ARGS))
(unquote (c and (r ARGS)))
()
))
1)
)
; takes a lisp tree and returns the hash of it
(defun sha256tree (TREE)
(if (l TREE)
(sha256 2 (sha256tree (f TREE)) (sha256tree (r TREE)))
(sha256 ONE TREE)))
(defun-inline mod_hash_for_singleton_struct (SINGLETON_STRUCT) (f SINGLETON_STRUCT))
(defun-inline launcher_id_for_singleton_struct (SINGLETON_STRUCT) (f (r SINGLETON_STRUCT)))
(defun-inline launcher_puzzle_hash_for_singleton_struct (SINGLETON_STRUCT) (r (r SINGLETON_STRUCT)))
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
(puzzle-hash-of-curried-function (mod_hash_for_singleton_struct SINGLETON_STRUCT)
inner_puzzle_hash
(sha256tree SINGLETON_STRUCT)
)
)
(defun-inline morph_condition (condition SINGLETON_STRUCT)
(c (f condition) (c (calculate_full_puzzle_hash SINGLETON_STRUCT (f (r condition))) (r (r condition))))
)
; Assert exactly one output with odd value exists - ignore it if value is -113
;; this function iterates over the output conditions from the inner puzzle & solution
;; and both checks that exactly one unique singleton child is created (with odd valued output),
;; and wraps the inner puzzle with this same singleton wrapper puzzle
;;
;; The special case where the output value is -113 means a child singleton is intentionally
;; *NOT* being created, thus forever ending this singleton's existence
(defun check_and_morph_conditions_for_singleton (SINGLETON_STRUCT conditions has_odd_output_been_found)
(if conditions
; check if it's an odd create coin
(if (and (= (f (f conditions)) CREATE_COIN) (logand (f (r (r (f conditions)))) ONE))
; check that we haven't already found one
(assert (not has_odd_output_been_found)
; then
(if (= (f (r (r (f conditions)))) -113)
; If it's the melt condition we don't bother prepending this condition
(check_and_morph_conditions_for_singleton SINGLETON_STRUCT (r conditions) ONE)
; If it isn't the melt condition, we morph it and prepend it
(c (morph_condition (f conditions) SINGLETON_STRUCT) (check_and_morph_conditions_for_singleton SINGLETON_STRUCT (r conditions) ONE))
)
)
(c (f conditions) (check_and_morph_conditions_for_singleton SINGLETON_STRUCT (r conditions) has_odd_output_been_found))
)
(assert has_odd_output_been_found ())
)
)
; assert that either the lineage proof is for a parent singleton, or, if it's for the launcher, verify it matched our launcher ID
; then return a condition asserting it actually is our parent ID
(defun verify_lineage_proof (SINGLETON_STRUCT parent_id is_not_launcher)
(assert (any is_not_launcher (= parent_id (launcher_id_for_singleton_struct SINGLETON_STRUCT)))
; then
(list ASSERT_MY_PARENT_ID parent_id)
)
)
; main
; if our value is not an odd amount then we are invalid
(assert (logand my_amount ONE)
; then
(c
(list ASSERT_MY_AMOUNT my_amount)
(c
; Verify the lineage proof by asserting our parent's ID
(verify_lineage_proof
SINGLETON_STRUCT
; calculate our parent's ID
(sha256
(parent_info_for_lineage_proof lineage_proof)
(if (is_not_eve_proof lineage_proof) ; The PH calculation changes based on the lineage proof
(calculate_full_puzzle_hash SINGLETON_STRUCT (puzzle_hash_for_lineage_proof lineage_proof)) ; wrap the innerpuz in a singleton
(launcher_puzzle_hash_for_singleton_struct SINGLETON_STRUCT) ; Use the static launcher puzzle hash
)
(if (is_not_eve_proof lineage_proof) ; The position of "amount" changes based on the type on lineage proof
(amount_for_lineage_proof lineage_proof)
(amount_for_eve_proof lineage_proof)
)
)
(is_not_eve_proof lineage_proof)
)
; finally check all of the conditions for a single odd output to wrap
(check_and_morph_conditions_for_singleton SINGLETON_STRUCT (a INNER_PUZZLE inner_solution) 0)
)
)
)
)

View File

@ -0,0 +1 @@
ff02ffff01ff02ffff03ffff18ff2fff3c80ffff01ff04ffff04ff10ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff0bff27ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff02ffff03ff77ffff0181b7ffff015780ff018080ffff04ff77ff808080808080ffff02ff26ffff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffff49ff4702ff33ff0401ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff02ffff03ffff02ffff03ffff09ff23ff1480ffff01ff02ffff03ffff18ff81b3ff3c80ffff01ff0101ff8080ff0180ff8080ff0180ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff3cff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff3cff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ff0bff2affff0bff3cff3880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff3cff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff28ffff04ff0bff808080ffff01ff088080ff0180ff018080

View File

@ -0,0 +1 @@
f1e8350cec62f8204aaf867cc3c12cae369f619258206616108c6cfd7be760b3

View File

@ -0,0 +1,17 @@
from typing import Any
from chia.types.blockchain_format.program import Program
def json_to_chialisp(json_data: Any) -> Any:
list_for_chialisp = []
if isinstance(json_data, list):
for value in json_data:
list_for_chialisp.append(json_to_chialisp(value))
else:
if isinstance(json_data, dict):
for key, value in json_data:
list_for_chialisp.append((key, json_to_chialisp(value)))
else:
list_for_chialisp = json_data
return Program.to(list_for_chialisp)

View File

@ -19,8 +19,9 @@ class WalletType(IntEnum):
RECOVERABLE = 7
DISTRIBUTED_ID = 8
POOLING_WALLET = 9
DATA_LAYER = 10
DATA_LAYER_OFFER = 11
NFT = 10
DATA_LAYER = 11
DATA_LAYER_OFFER = 12
class AmountWithPuzzlehash(TypedDict):

View File

@ -210,7 +210,7 @@ class Wallet:
me=None,
coin_announcements: Optional[Set[bytes]] = None,
coin_announcements_to_assert: Optional[Set[bytes32]] = None,
puzzle_announcements: Optional[Set[bytes32]] = None,
puzzle_announcements: Optional[Set[bytes]] = None,
puzzle_announcements_to_assert: Optional[Set[bytes32]] = None,
fee=0,
) -> Program:
@ -314,7 +314,7 @@ class Wallet:
max_send = await self.get_max_send_amount()
if total_amount > max_send:
raise ValueError(f"Can't send more than {max_send} in a single transaction")
self.log.debug("Got back max send amount: %s", max_send)
if coins is None:
coins = await self.select_coins(uint64(total_amount))
assert len(coins) > 0
@ -433,6 +433,7 @@ class Wallet:
else:
non_change_amount = uint64(amount + sum(p["amount"] for p in primaries))
self.log.debug("Generating transaction for: %s %s %s", puzzle_hash, amount, repr(coins))
transaction = await self._generate_unsigned_transaction(
amount,
puzzle_hash,
@ -448,8 +449,7 @@ class Wallet:
in_transaction=in_transaction,
)
assert len(transaction) > 0
self.log.info("About to sign a transaction")
self.log.info("About to sign a transaction: %s", transaction)
await self.hack_populate_secret_keys_for_coin_spends(transaction)
spend_bundle: SpendBundle = await sign_coin_spends(
transaction,
@ -494,7 +494,7 @@ class Wallet:
await self.wallet_state_manager.wallet_node.update_ui()
# This is to be aggregated together with a CAT offer to ensure that the trade happens
async def create_spend_bundle_relative_chia(self, chia_amount: int, exclude: List[Coin]) -> SpendBundle:
async def create_spend_bundle_relative_chia(self, chia_amount: int, exclude: List[Coin] = []) -> SpendBundle:
list_of_solutions = []
utxos = None

View File

@ -819,6 +819,8 @@ class WalletNode:
# untrusted peers. For trusted, we always process the state, and we process reorgs as well.
assert self.wallet_state_manager is not None
assert self.server is not None
for coin in request.items:
self.log.info(f"request coin: {coin.coin.name()}{coin}")
async with self.wallet_state_manager.lock:
await self.receive_state_from_peer(

View File

@ -91,6 +91,7 @@ class WalletPuzzleStore:
try:
sql_records = []
for record in records:
log.debug("Adding derivation record: %s", record)
self.all_puzzle_hashes.add(record.puzzle_hash)
if record.hardened:
hardened = 1

View File

@ -7,19 +7,20 @@ import time
from collections import defaultdict
from pathlib import Path
from secrets import token_bytes
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple
import aiosqlite
from blspy import G1Element, PrivateKey
from chia.consensus.coinbase import pool_parent_id, farmer_parent_id
from chia.consensus.coinbase import farmer_parent_id, pool_parent_id
from chia.consensus.constants import ConsensusConstants
from chia.data_layer.data_layer_wallet import DataLayerWallet
from chia.data_layer.dl_wallet_store import DataLayerStore
from chia.pools.pool_puzzles import SINGLETON_LAUNCHER_HASH, solution_to_pool_state
from chia.pools.pool_wallet import PoolWallet
from chia.protocols import wallet_protocol
from chia.protocols.wallet_protocol import PuzzleSolutionResponse, RespondPuzzleSolution, CoinState
from chia.protocols.wallet_protocol import CoinState, PuzzleSolutionResponse, RespondPuzzleSolution
from chia.server.server import ChiaServer
from chia.server.ws_connection import WSChiaConnection
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
@ -29,16 +30,20 @@ from chia.types.full_block import FullBlock
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.util.byte_types import hexstr_to_bytes
from chia.util.config import process_config_start_method
from chia.util.db_synchronous import db_synchronous_on
from chia.util.db_wrapper import DBWrapper
from chia.util.errors import Err
from chia.util.ints import uint32, uint64, uint128, uint8
from chia.util.db_synchronous import db_synchronous_on
from chia.wallet.cat_wallet.cat_utils import match_cat_puzzle, construct_cat_puzzle
from chia.wallet.cat_wallet.cat_wallet import CATWallet
from chia.util.ints import uint8, uint32, uint64, uint128
from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
from chia.wallet.cat_wallet.cat_utils import construct_cat_puzzle, match_cat_puzzle
from chia.wallet.cat_wallet.cat_wallet import CATWallet
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.did_wallet.did_wallet_puzzles import DID_INNERPUZ_MOD, create_fullpuz, match_did_puzzle
from chia.wallet.key_val_store import KeyValStore
from chia.wallet.nft_wallet.nft_wallet import NFTWallet, NFTWalletInfo
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.puzzle_drivers import PuzzleInfo
from chia.wallet.puzzles.cat_loader import CAT_MOD
@ -63,8 +68,6 @@ from chia.wallet.wallet_puzzle_store import WalletPuzzleStore
from chia.wallet.wallet_sync_store import WalletSyncStore
from chia.wallet.wallet_transaction_store import WalletTransactionStore
from chia.wallet.wallet_user_store import WalletUserStore
from chia.server.server import ChiaServer
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.wallet_weight_proof_handler import WalletWeightProofHandler
@ -207,6 +210,12 @@ class WalletStateManager:
self.main_wallet,
wallet_info,
)
elif wallet_info.type == WalletType.NFT:
wallet = await NFTWallet.create(
self,
self.main_wallet,
wallet_info,
)
elif wallet_info.type == WalletType.POOLING_WALLET:
wallet = await PoolWallet.create_from_db(
self,
@ -248,11 +257,12 @@ class WalletStateManager:
that we can restore the wallet from only the private keys.
"""
targets = list(self.wallets.keys())
self.log.debug("Target wallets to generate puzzle hashes for: %s", repr(targets))
unused: Optional[uint32] = await self.puzzle_store.get_unused_derivation_path()
if unused is None:
# This handles the case where the database has entries but they have all been used
unused = await self.puzzle_store.get_last_derivation_path()
self.log.debug("Tried finding unused: %s", unused)
if unused is None:
# This handles the case where the database is empty
unused = uint32(0)
@ -368,11 +378,13 @@ class WalletStateManager:
# If we have no unused public keys, we will create new ones
unused: Optional[uint32] = await self.puzzle_store.get_unused_derivation_path()
if unused is None:
self.log.debug("No unused paths, generate more ")
await self.create_more_puzzle_hashes()
# Now we must have unused public keys
unused = await self.puzzle_store.get_unused_derivation_path()
assert unused is not None
# Now we must have unused public keys
unused = await self.puzzle_store.get_unused_derivation_path()
assert unused is not None
self.log.debug("Fetching derivation record for: %s %s %s", unused, wallet_id, hardened)
record: Optional[DerivationRecord] = await self.puzzle_store.get_derivation_record(
unused, wallet_id, hardened
)
@ -537,7 +549,7 @@ class WalletStateManager:
removals[coin.name()] = coin
return removals
async def fetch_parent_and_check_for_cat(
async def determine_coin_type(
self, peer: WSChiaConnection, coin_state: CoinState, fork_height: Optional[uint32]
) -> Tuple[Optional[uint32], Optional[WalletType]]:
if self.is_pool_reward(coin_state.created_height, coin_state.coin.parent_coin_info) or self.is_farmer_reward(
@ -553,55 +565,190 @@ class WalletStateManager:
return None, None
parent_coin_state = response[0]
assert parent_coin_state.spent_height == coin_state.created_height
wallet_id = None
wallet_type = None
cs: Optional[CoinSpend] = await self.wallet_node.fetch_puzzle_solution(
coin_spend: Optional[CoinSpend] = await self.wallet_node.fetch_puzzle_solution(
peer, parent_coin_state.spent_height, parent_coin_state.coin
)
if cs is None:
if coin_spend is None:
return None, None
matched, curried_args = match_cat_puzzle(Program.from_bytes(bytes(cs.puzzle_reveal)))
if matched:
mod_hash, tail_hash, inner_puzzle = curried_args
inner_puzzle_hash = inner_puzzle.get_tree_hash()
self.log.info(
f"parent: {parent_coin_state.coin.name()} inner_puzzle_hash for parent is {inner_puzzle_hash}"
)
# Check if the coin is a CAT
cat_matched, cat_curried_args = match_cat_puzzle(Program.from_bytes(bytes(coin_spend.puzzle_reveal)))
if cat_matched:
return await self.handle_cat(cat_curried_args, parent_coin_state, coin_state, coin_spend)
hint_list = compute_coin_hints(cs)
derivation_record = None
for hint in hint_list:
derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(bytes32(hint))
if derivation_record is not None:
break
# Check if the coin is a NFT
# hint
# First spend where 1 mojo coin -> Singleton launcher -> NFT -> NFT
try:
uncurried_nft = UncurriedNFT.uncurry(Program.from_bytes(bytes(coin_spend.puzzle_reveal)))
except Exception:
# This is not a NFT coin, skip NFT handling
pass
else:
return await self.handle_nft(coin_spend, uncurried_nft)
if derivation_record is None:
self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
# Check if the coin is a DID
did_matched, did_curried_args = match_did_puzzle(Program.from_bytes(bytes(coin_spend.puzzle_reveal)))
if did_matched:
return await self.handle_did(did_curried_args, parent_coin_state, coin_state, coin_spend)
return None, None
async def handle_cat(
self,
curried_args: Iterator[Program],
parent_coin_state: CoinState,
coin_state: CoinState,
coin_spend: CoinSpend,
) -> Tuple[Optional[uint32], Optional[WalletType]]:
"""
Handle the new coin when it is a CAT
:param curried_args: Curried arg of the CAT mod
:param parent_coin_state: Parent coin state
:param coin_state: Current coin state
:param coin_spend: New coin spend
:return: Wallet ID & Wallet Type
"""
wallet_id = None
wallet_type = None
mod_hash, tail_hash, inner_puzzle = curried_args
inner_puzzle_hash = inner_puzzle.get_tree_hash()
self.log.info(f"parent: {parent_coin_state.coin.name()} inner_puzzle_hash for parent is {inner_puzzle_hash}")
hint_list = compute_coin_hints(coin_spend)
derivation_record = None
for hint in hint_list:
derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(bytes32(hint))
if derivation_record is not None:
break
if derivation_record is None:
self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
else:
our_inner_puzzle: Program = self.main_wallet.puzzle_for_pk(bytes(derivation_record.pubkey))
asset_id: bytes32 = bytes32(bytes(tail_hash)[1:])
cat_puzzle = construct_cat_puzzle(CAT_MOD, asset_id, our_inner_puzzle)
if cat_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash:
return None, None
if bytes(tail_hash).hex()[2:] in self.default_cats or self.config.get(
"automatically_add_unknown_cats", False
):
cat_wallet = await CATWallet.create_wallet_for_cat(
self, self.main_wallet, bytes(tail_hash).hex()[2:], in_transaction=True
)
wallet_id = cat_wallet.id()
wallet_type = WalletType(cat_wallet.type())
self.state_changed("wallet_created")
else:
our_inner_puzzle: Program = self.main_wallet.puzzle_for_pk(bytes(derivation_record.pubkey))
asset_id: bytes32 = bytes32(bytes(tail_hash)[1:])
cat_puzzle = construct_cat_puzzle(CAT_MOD, asset_id, our_inner_puzzle)
if cat_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash:
return None, None
if bytes(tail_hash).hex()[2:] in self.default_cats or self.config.get(
"automatically_add_unknown_cats", False
):
cat_wallet = await CATWallet.create_wallet_for_cat(
self, self.main_wallet, bytes(tail_hash).hex()[2:], in_transaction=True
)
wallet_id = cat_wallet.id()
wallet_type = WalletType(cat_wallet.type())
self.state_changed("wallet_created")
# Found unacknowledged CAT, save it in the database.
await self.interested_store.add_unacknowledged_token(
asset_id,
CATWallet.default_wallet_name_for_unknown_cat(asset_id.hex()),
parent_coin_state.spent_height,
parent_coin_state.coin.puzzle_hash,
)
self.state_changed("added_stray_cat")
return wallet_id, wallet_type
async def handle_did(
self,
curried_args: Iterator[Program],
parent_coin_state: CoinState,
coin_state: CoinState,
coin_spend: CoinSpend,
) -> Tuple[Optional[uint32], Optional[WalletType]]:
"""
Handle the new coin when it is a DID
:param curried_args: Curried arg of the DID mod
:param parent_coin_state: Parent coin state
:param coin_state: Current coin state
:param coin_spend: New coin spend
:return: Wallet ID & Wallet Type
"""
wallet_id = None
wallet_type = None
p2_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata = curried_args
inner_puzzle_hash = p2_puzzle.get_tree_hash()
self.log.info(f"parent: {parent_coin_state.coin.name()} inner_puzzle_hash for parent is {inner_puzzle_hash}")
hint_list = compute_coin_hints(coin_spend)
derivation_record = None
for hint in hint_list:
derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(bytes32(hint))
if derivation_record is not None:
break
if derivation_record is None:
self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
else:
our_inner_puzzle: Program = self.main_wallet.puzzle_for_pk(bytes(derivation_record.pubkey))
launch_id: bytes32 = bytes32(bytes(singleton_struct.rest().first())[1:])
self.log.info(f"Found DID, launch_id {launch_id}.")
did_puzzle = DID_INNERPUZ_MOD.curry(
our_inner_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata
)
full_puzzle = create_fullpuz(did_puzzle, launch_id)
did_puzzle_empty_recovery = DID_INNERPUZ_MOD.curry(
our_inner_puzzle, Program.to([]).get_tree_hash(), uint64(0), singleton_struct, metadata
)
full_puzzle_empty_recovery = create_fullpuz(did_puzzle_empty_recovery, launch_id)
if full_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash:
if full_puzzle_empty_recovery.get_tree_hash() == coin_state.coin.puzzle_hash:
did_puzzle = did_puzzle_empty_recovery
self.log.info("DID recovery list was reset by the previous owner.")
else:
# Found unacknowledged CAT, save it in the database.
await self.interested_store.add_unacknowledged_token(
asset_id,
CATWallet.default_wallet_name_for_unknown_cat(asset_id.hex()),
parent_coin_state.spent_height,
parent_coin_state.coin.puzzle_hash,
)
self.state_changed("added_stray_cat")
self.log.error("DID puzzle hash doesn't match, please check curried parameters.")
return None, None
# Create DID wallet
response: List[CoinState] = await self.wallet_node.get_coin_state([launch_id])
if len(response) == 0:
self.log.warning(f"Could not find the launch coin with ID: {launch_id}")
return None, None
launch_coin: CoinState = response[0]
did_wallet = await DIDWallet.create_new_did_wallet_from_coin_spend(
self, self.main_wallet, launch_coin.coin, did_puzzle, coin_spend, f"DID {launch_id.hex()}"
)
wallet_id = did_wallet.id()
wallet_type = WalletType(did_wallet.type())
self.state_changed("wallet_created")
return wallet_id, wallet_type
async def handle_nft(
self, coin_spend: CoinSpend, uncurried_nft: UncurriedNFT
) -> Tuple[Optional[uint32], Optional[WalletType]]:
"""
Handle the new coin when it is a NFT
:param coin_spend: New coin spend
:param uncurried_nft: Uncurried NFT
:return: Wallet ID & Wallet Type
"""
wallet_id = None
wallet_type = None
self.log.debug("Handling NFT: %s", coin_spend)
for wallet_info in await self.get_all_wallet_info_entries():
if wallet_info.type == WalletType.NFT:
nft_wallet_info = NFTWalletInfo.from_json_dict(json.loads(wallet_info.data))
self.log.debug(
"Checking NFT wallet %r and inner puzzle %s",
wallet_info.name,
uncurried_nft.inner_puzzle.get_tree_hash(),
)
if not nft_wallet_info.did_wallet_id:
# standard NFT wallet
wallet_id = wallet_info.id
wallet_type = WalletType.NFT
break
if wallet_id is None:
# TODO Modify this for NFT1
self.log.info("Cannot find a NFT wallet, creating a new one.")
nft_wallet: NFTWallet = await NFTWallet.create_new_nft_wallet(
self, self.main_wallet, name="NFT Wallet", in_transaction=True
)
wallet_id = uint32(nft_wallet.wallet_id)
wallet_type = WalletType.NFT
return wallet_id, wallet_type
@ -653,7 +800,7 @@ class WalletStateManager:
wallet_id = uint32(local_record.wallet_id)
wallet_type = local_record.wallet_type
elif coin_state.created_height is not None:
wallet_id, wallet_type = await self.fetch_parent_and_check_for_cat(peer, coin_state, fork_height)
wallet_id, wallet_type = await self.determine_coin_type(peer, coin_state, fork_height)
potential_dl = self.get_dl_wallet()
if potential_dl is not None:
if await potential_dl.get_singleton_record(coin_state.coin.name()) is not None:
@ -680,8 +827,10 @@ class WalletStateManager:
# TODO implements this coin got reorged
# TODO: we need to potentially roll back the pool wallet here
pass
# if the new coin has not been spent (i.e not ephemeral)
elif coin_state.created_height is not None and coin_state.spent_height is None:
await self.coin_added(coin_state.coin, coin_state.created_height, all_txs, wallet_id, wallet_type)
# if the coin has been spent
elif coin_state.created_height is not None and coin_state.spent_height is not None:
self.log.info(f"Coin Removed: {coin_state}")
record = await self.coin_store.get_coin_record(coin_state.coin.name())
@ -852,6 +1001,11 @@ class WalletStateManager:
in_transaction=in_transaction,
)
elif record.wallet_type == WalletType.NFT:
if coin_state.spent_height is not None:
nft_wallet = self.wallets[uint32(record.wallet_id)]
await nft_wallet.remove_coin(coin_state.coin, in_transaction=True)
# Check if a child is a singleton launcher
if children is None:
children = await self.wallet_node.fetch_children(peer, coin_state.coin.name(), fork_height)
@ -987,7 +1141,9 @@ class WalletStateManager:
if existing is not None:
return None
self.log.info(f"Adding coin: {coin} at {height} wallet_id:{wallet_id}")
self.log.info(
f"Adding record to state manager coin: {coin} at {height} wallet_id:{wallet_id} and type: {wallet_type}"
)
farmer_reward = False
pool_reward = False
if self.is_farmer_reward(height, coin.parent_coin_info):
@ -1072,6 +1228,9 @@ class WalletStateManager:
wallet = self.wallets[wallet_id]
await wallet.coin_added(coin, height)
if wallet_type == WalletType.NFT:
await self.wallets[wallet_id].add_nft_coin(coin, height, in_transaction=True)
await self.create_more_puzzle_hashes(in_transaction=True)
return coin_record_1

File diff suppressed because one or more lines are too long

View File

@ -112,6 +112,7 @@ kwargs = dict(
"chia.wallet.rl_wallet",
"chia.wallet.cat_wallet",
"chia.wallet.did_wallet",
"chia.wallet.nft_wallet",
"chia.wallet.settings",
"chia.wallet.trading",
"chia.wallet.util",

View File

@ -44,6 +44,13 @@ wallet_program_files = set(
"chia/wallet/puzzles/delegated_tail.clvm",
"chia/wallet/puzzles/settlement_payments.clvm",
"chia/wallet/puzzles/genesis_by_coin_id.clvm",
"chia/wallet/puzzles/singleton_top_layer_v1_1.clvm",
"chia/wallet/puzzles/nft_innerpuz.clvm",
"chia/wallet/puzzles/nft_transfer_program.clvm",
"chia/wallet/puzzles/nft_ownership_transfer_program.clvm",
"chia/wallet/puzzles/nft_metadata_updater.clvm",
"chia/wallet/puzzles/nft_metadata_updater_default.clvm",
"chia/wallet/puzzles/nft_state_layer.clvm",
"chia/wallet/puzzles/database_offer.clvm",
"chia/wallet/puzzles/database_layer.clvm",
"chia/wallet/puzzles/singleton_top_layer_atari_only.clvm",

View File

@ -0,0 +1,3 @@
# flake8: noqa: E501
job_timeout = 50
checkout_blocks_and_plots = True

View File

@ -1,3 +1,6 @@
import json
from typing import Optional
import pytest
from blspy import AugSchemeMPL
@ -7,10 +10,16 @@ from chia.types.blockchain_format.program import Program
from chia.types.peer_info import PeerInfo
from chia.types.spend_bundle import SpendBundle
from chia.util.ints import uint16, uint32, uint64
from chia.wallet.util.wallet_types import WalletType
from chia.wallet.did_wallet.did_wallet import DIDWallet
from tests.time_out_assert import time_out_assert, time_out_assert_not_none
pytestmark = pytest.mark.skip("TODO: Fix tests")
# pytestmark = pytest.mark.skip("TODO: Fix tests")
async def get_wallet_num(wallet_manager):
return len(await wallet_manager.get_all_wallet_info_entries())
class TestDIDWallet:
@ -58,6 +67,11 @@ class TestDIDWallet:
wallet_node_0.wallet_state_manager, wallet_0, uint64(101)
)
spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_0.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
@ -72,6 +86,11 @@ class TestDIDWallet:
wallet_node_1.wallet_state_manager, wallet_1, uint64(201), backup_ids
)
spend_bundle_list = await wallet_node_1.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_1.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
@ -79,24 +98,29 @@ class TestDIDWallet:
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 201)
await time_out_assert(15, did_wallet_1.get_pending_change_balance, 0)
filename = "test.backup"
did_wallet_1.create_backup(filename)
backup_data = did_wallet_1.create_backup()
# Wallet2 recovers DIDWallet2 to a new set of keys
async with wallet_node_2.wallet_state_manager.lock:
did_wallet_2 = await DIDWallet.create_new_did_wallet_from_recovery(
wallet_node_2.wallet_state_manager, wallet_2, filename
wallet_node_2.wallet_state_manager, wallet_2, backup_data
)
coins = await did_wallet_1.select_coins(1)
coin = coins.copy().pop()
assert did_wallet_2.did_info.temp_coin == coin
newpuzhash = await did_wallet_2.get_new_inner_hash()
newpuzhash = await did_wallet_2.get_new_did_inner_hash()
pubkey = bytes(
(await did_wallet_2.wallet_state_manager.get_unused_derivation_record(did_wallet_2.wallet_info.id)).pubkey
)
message_spend_bundle = await did_wallet_0.create_attestment(
did_wallet_2.did_info.temp_coin.name(), newpuzhash, pubkey, "test.attest"
message_spend_bundle, attest_data = await did_wallet_0.create_attestment(
did_wallet_2.did_info.temp_coin.name(), newpuzhash, pubkey
)
spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_0.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
print(f"pubkey: {pubkey}")
for i in range(1, num_blocks):
@ -105,10 +129,10 @@ class TestDIDWallet:
(
test_info_list,
test_message_spend_bundle,
) = await did_wallet_2.load_attest_files_for_recovery_spend(["test.attest"])
) = await did_wallet_2.load_attest_files_for_recovery_spend([attest_data])
assert message_spend_bundle == test_message_spend_bundle
await did_wallet_2.recovery_spend(
spend_bundle = await did_wallet_2.recovery_spend(
did_wallet_2.did_info.temp_coin,
newpuzhash,
test_info_list,
@ -117,6 +141,8 @@ class TestDIDWallet:
)
print(f"pubkey: {did_wallet_2}")
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
@ -126,6 +152,13 @@ class TestDIDWallet:
some_ph = 32 * b"\2"
await did_wallet_2.create_exit_spend(some_ph)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_2.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
@ -143,8 +176,8 @@ class TestDIDWallet:
async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_wallet_nodes):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_1 = full_nodes[0]
server_1 = full_node_1.server
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
@ -156,7 +189,7 @@ class TestDIDWallet:
await server_3.start_client(PeerInfo(self_hostname, uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
@ -171,10 +204,15 @@ class TestDIDWallet:
did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node.wallet_state_manager, wallet, uint64(101)
)
assert did_wallet.wallet_info.name == "Profile 1"
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph = await wallet2.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101)
@ -186,8 +224,13 @@ class TestDIDWallet:
wallet_node_2.wallet_state_manager, wallet2, uint64(101), recovery_list
)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet2.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet_2.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_2.get_unconfirmed_balance, 101)
@ -201,9 +244,14 @@ class TestDIDWallet:
wallet_node_2.wallet_state_manager, wallet2, uint64(201), recovery_list
)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet2.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
assert did_wallet_3.did_info.backup_ids == recovery_list
await time_out_assert(15, did_wallet_3.get_confirmed_balance, 201)
@ -211,36 +259,52 @@ class TestDIDWallet:
coins = await did_wallet_3.select_coins(1)
coin = coins.pop()
filename = "test.backup"
did_wallet_3.create_backup(filename)
backup_data = did_wallet_3.create_backup()
async with wallet_node.wallet_state_manager.lock:
did_wallet_4 = await DIDWallet.create_new_did_wallet_from_recovery(
wallet_node.wallet_state_manager,
wallet,
filename,
backup_data,
)
assert did_wallet_4.wallet_info.name == "Profile 2"
pubkey = (
await did_wallet_4.wallet_state_manager.get_unused_derivation_record(did_wallet_2.wallet_info.id)
).pubkey
new_ph = await did_wallet_4.get_new_inner_hash()
message_spend_bundle = await did_wallet.create_attestment(coin.name(), new_ph, pubkey, "test1.attest")
message_spend_bundle2 = await did_wallet_2.create_attestment(coin.name(), new_ph, pubkey, "test2.attest")
new_ph = await did_wallet_4.get_new_did_inner_hash()
message_spend_bundle, attest1 = await did_wallet.create_attestment(coin.name(), new_ph, pubkey)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
message_spend_bundle2, attest2 = await did_wallet_2.create_attestment(coin.name(), new_ph, pubkey)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_2.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
message_spend_bundle = message_spend_bundle.aggregate([message_spend_bundle, message_spend_bundle2])
(
test_info_list,
test_message_spend_bundle,
) = await did_wallet_4.load_attest_files_for_recovery_spend(["test1.attest", "test2.attest"])
) = await did_wallet_4.load_attest_files_for_recovery_spend([attest1, attest2])
assert message_spend_bundle == test_message_spend_bundle
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, message_spend_bundle)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_4.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_4.get_confirmed_balance, 201)
await time_out_assert(15, did_wallet_4.get_unconfirmed_balance, 201)
@ -251,8 +315,8 @@ class TestDIDWallet:
async def test_did_recovery_with_empty_set(self, self_hostname, two_wallet_nodes):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_1 = full_nodes[0]
server_1 = full_node_1.server
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
@ -263,7 +327,7 @@ class TestDIDWallet:
await server_3.start_client(PeerInfo(self_hostname, uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
@ -279,27 +343,37 @@ class TestDIDWallet:
wallet_node.wallet_state_manager, wallet, uint64(101)
)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101)
coins = await did_wallet.select_coins(1)
coin = coins.pop()
print(f"Old coin id {coin.name()}")
info = Program.to([])
pubkey = (await did_wallet.wallet_state_manager.get_unused_derivation_record(did_wallet.wallet_info.id)).pubkey
spend_bundle = await did_wallet.recovery_spend(
coin, ph, info, pubkey, SpendBundle([], AugSchemeMPL.aggregate([]))
)
additions = spend_bundle.additions()
assert additions == []
try:
spend_bundle = await did_wallet.recovery_spend(
coin, ph, info, pubkey, SpendBundle([], AugSchemeMPL.aggregate([]))
)
except Exception:
# We expect a CLVM 80 error for this test
pass
else:
assert False
@pytest.mark.asyncio
async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_1 = full_nodes[0]
server_1 = full_node_1.server
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
@ -309,7 +383,7 @@ class TestDIDWallet:
await server_2.start_client(PeerInfo(self_hostname, uint16(server_1._port)), None)
await server_3.start_client(PeerInfo(self_hostname, uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
@ -324,10 +398,13 @@ class TestDIDWallet:
did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node.wallet_state_manager, wallet, uint64(101)
)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet2.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101)
@ -337,11 +414,15 @@ class TestDIDWallet:
did_wallet_2: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node_2.wallet_state_manager, wallet2, uint64(101), recovery_list
)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet2.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet_2.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_2.get_unconfirmed_balance, 101)
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(25, did_wallet_2.get_confirmed_balance, 101)
await time_out_assert(25, did_wallet_2.get_unconfirmed_balance, 101)
assert did_wallet_2.did_info.backup_ids == recovery_list
# Update coin with new ID info
@ -350,77 +431,297 @@ class TestDIDWallet:
assert did_wallet.did_info.backup_ids == recovery_list
await did_wallet.create_update_spend()
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101)
# DID Wallet 2 recovers into DID Wallet 3 with new innerpuz
filename = "test.backup"
did_wallet_2.create_backup(filename)
backup_data = did_wallet_2.create_backup()
async with wallet_node.wallet_state_manager.lock:
did_wallet_3 = await DIDWallet.create_new_did_wallet_from_recovery(
wallet_node.wallet_state_manager,
wallet,
filename,
backup_data,
)
new_ph = await did_wallet_3.get_new_inner_hash()
new_ph = await did_wallet_3.get_new_did_inner_hash()
coins = await did_wallet_2.select_coins(1)
coin = coins.pop()
pubkey = (
await did_wallet_3.wallet_state_manager.get_unused_derivation_record(did_wallet_3.wallet_info.id)
).pubkey
message_spend_bundle = await did_wallet.create_attestment(coin.name(), new_ph, pubkey, "test.attest")
attest_data = (await did_wallet.create_attestment(coin.name(), new_ph, pubkey))[1]
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
(
info,
message_spend_bundle,
) = await did_wallet_3.load_attest_files_for_recovery_spend(["test.attest"])
) = await did_wallet_3.load_attest_files_for_recovery_spend([attest_data])
await did_wallet_3.recovery_spend(coin, new_ph, info, pubkey, message_spend_bundle)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_3.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet_3.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_3.get_unconfirmed_balance, 101)
# DID Wallet 1 recovery spends into DID Wallet 4
filename = "test.backup"
did_wallet.create_backup(filename)
backup_data = did_wallet.create_backup()
async with wallet_node_2.wallet_state_manager.lock:
did_wallet_4 = await DIDWallet.create_new_did_wallet_from_recovery(
wallet_node_2.wallet_state_manager,
wallet2,
filename,
backup_data,
)
coins = await did_wallet.select_coins(1)
coin = coins.pop()
new_ph = await did_wallet_4.get_new_inner_hash()
new_ph = await did_wallet_4.get_new_did_inner_hash()
pubkey = (
await did_wallet_4.wallet_state_manager.get_unused_derivation_record(did_wallet_4.wallet_info.id)
).pubkey
await did_wallet_3.create_attestment(coin.name(), new_ph, pubkey, "test.attest")
attest1 = (await did_wallet_3.create_attestment(coin.name(), new_ph, pubkey))[1]
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_3.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
(
test_info_list,
test_message_spend_bundle,
) = await did_wallet_4.load_attest_files_for_recovery_spend(["test.attest"])
spend_bundle = await did_wallet_4.recovery_spend(
coin, new_ph, test_info_list, pubkey, test_message_spend_bundle
) = await did_wallet_4.load_attest_files_for_recovery_spend([attest1])
await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, test_message_spend_bundle)
spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_4.id()
)
await time_out_assert_not_none(15, full_node_1.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, did_wallet_4.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_4.get_unconfirmed_balance, 101)
await time_out_assert(15, did_wallet.get_confirmed_balance, 0)
await time_out_assert(15, did_wallet.get_unconfirmed_balance, 0)
@pytest.mark.parametrize(
"with_recovery",
[True, False],
)
@pytest.mark.asyncio
async def test_did_transfer(self, two_wallet_nodes, with_recovery):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
wallet2 = wallet_node_2.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
wallet_node.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_2.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
await server_3.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i))
for i in range(1, num_blocks - 1)
]
)
await time_out_assert(15, wallet.get_confirmed_balance, funds)
async with wallet_node.wallet_state_manager.lock:
did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node.wallet_state_manager,
wallet,
uint64(101),
[bytes(ph)],
uint64(1),
{"Twitter": "Test", "GitHub": "测试"},
)
assert did_wallet_1.wallet_info.name == "Profile 1"
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet2.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
# Transfer DID
new_puzhash = await wallet2.get_new_puzzlehash()
await did_wallet_1.transfer_did(new_puzhash, uint64(0), with_recovery)
print(f"Original launch_id {did_wallet_1.did_info.origin_coin.name()}")
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_1.id()
)
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet2.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
# Check if the DID wallet is created in the wallet2
await time_out_assert(30, len, 2, wallet_node_2.wallet_state_manager.wallets)
# Get the new DID wallet
did_wallets = list(
filter(
lambda w: (w.type == WalletType.DISTRIBUTED_ID),
await wallet_node_2.wallet_state_manager.get_all_wallet_info_entries(),
)
)
did_wallet_2: Optional[DIDWallet] = wallet_node_2.wallet_state_manager.wallets[did_wallets[0].id]
assert did_wallet_1.did_info.origin_coin == did_wallet_2.did_info.origin_coin
if with_recovery:
assert did_wallet_1.did_info.backup_ids[0] == did_wallet_2.did_info.backup_ids[0]
assert did_wallet_1.did_info.num_of_backup_ids_needed == did_wallet_2.did_info.num_of_backup_ids_needed
metadata = json.loads(did_wallet_1.did_info.metadata)
assert metadata["Twitter"] == "Test"
assert metadata["GitHub"] == "测试"
@pytest.mark.asyncio
async def test_update_recovery_list(self, two_wallet_nodes):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
wallet_node.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_2.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
await server_3.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i))
for i in range(1, num_blocks - 1)
]
)
await time_out_assert(15, wallet.get_confirmed_balance, funds)
async with wallet_node.wallet_state_manager.lock:
did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node.wallet_state_manager, wallet, uint64(101), []
)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
await did_wallet_1.update_recovery_list([bytes(ph)], 1)
await did_wallet_1.create_update_spend()
ph2 = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
assert did_wallet_1.did_info.backup_ids[0] == bytes(ph)
assert did_wallet_1.did_info.num_of_backup_ids_needed == 1
@pytest.mark.asyncio
async def test_update_metadata(self, two_wallet_nodes):
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
server_1 = full_node_api.server
wallet_node, server_2 = wallets[0]
wallet_node_2, server_3 = wallets[1]
wallet = wallet_node.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
wallet_node.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_2.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
await server_3.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[
calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i))
for i in range(1, num_blocks - 1)
]
)
await time_out_assert(15, wallet.get_confirmed_balance, funds)
async with wallet_node.wallet_state_manager.lock:
did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node.wallet_state_manager, wallet, uint64(101), []
)
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet.id())
spend_bundle = spend_bundle_list[0].spend_bundle
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
ph2 = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
metadata = {}
metadata["Twitter"] = "http://www.twitter.com"
await did_wallet_1.update_metadata(metadata)
await did_wallet_1.create_update_spend()
ph2 = await wallet.get_new_puzzlehash()
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
assert did_wallet_1.did_info.metadata.find("Twitter") > 0

View File

@ -1,121 +0,0 @@
import logging
import pytest
from chia.rpc.rpc_server import start_rpc_server
from chia.rpc.wallet_rpc_api import WalletRpcApi
from chia.rpc.wallet_rpc_client import WalletRpcClient
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
from chia.types.peer_info import PeerInfo
from chia.util.ints import uint16, uint64
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.util.wallet_types import WalletType
from tests.time_out_assert import time_out_assert
log = logging.getLogger(__name__)
pytestmark = pytest.mark.skip("TODO: Fix tests")
class TestDIDWallet:
@pytest.mark.asyncio
async def test_create_did(self, bt, three_wallet_nodes, self_hostname):
num_blocks = 4
full_nodes, wallets = three_wallet_nodes
full_node_api = full_nodes[0]
full_node_server = full_node_api.server
wallet_node_0, wallet_server_0 = wallets[0]
wallet_node_1, wallet_server_1 = wallets[1]
wallet_node_2, wallet_server_2 = wallets[2]
MAX_WAIT_SECS = 30
wallet = wallet_node_0.wallet_state_manager.main_wallet
ph = await wallet.get_new_puzzlehash()
await wallet_server_0.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
await wallet_server_1.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
await wallet_server_2.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
for i in range(0, num_blocks + 1):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
log.info("Waiting for initial money in Wallet 0 ...")
api_one = WalletRpcApi(wallet_node_0)
config = bt.config
daemon_port = config["daemon_port"]
await wallet_server_0.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None)
rpc_server_cleanup, test_rpc_port = await start_rpc_server(
api_one,
self_hostname,
daemon_port,
uint16(0),
lambda x: None,
bt.root_path,
config,
connect_to_daemon=False,
)
client = await WalletRpcClient.create(self_hostname, test_rpc_port, bt.root_path, bt.config)
async def got_initial_money():
balances = await client.get_wallet_balance("1")
return balances["confirmed_wallet_balance"] > 0
await time_out_assert(timeout=MAX_WAIT_SECS, function=got_initial_money)
val = await client.create_new_did_wallet(201)
assert isinstance(val, dict)
if "success" in val:
assert val["success"]
assert val["type"] == WalletType.DISTRIBUTED_ID.value
assert val["wallet_id"] > 1
assert len(val["my_did"]) == 64
assert bytes.fromhex(val["my_did"])
main_wallet_2 = wallet_node_2.wallet_state_manager.main_wallet
ph2 = await main_wallet_2.get_new_puzzlehash()
for i in range(0, num_blocks + 1):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
recovery_list = [bytes.fromhex(val["my_did"])]
async with wallet_node_2.wallet_state_manager.lock:
did_wallet_2: DIDWallet = await DIDWallet.create_new_did_wallet(
wallet_node_2.wallet_state_manager, main_wallet_2, uint64(101), recovery_list
)
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
filename = "test.backup"
did_wallet_2.create_backup(filename)
val = await client.create_new_did_wallet_from_recovery(filename)
if "success" in val:
assert val["success"]
assert val["type"] == WalletType.DISTRIBUTED_ID.value
assert val["wallet_id"] > 1
did_wallet_id_3 = val["wallet_id"]
assert len(val["my_did"]) == 64
assert bytes.fromhex(val["my_did"]) == did_wallet_2.did_info.origin_coin.name()
assert bytes.fromhex(val["coin_name"])
assert bytes.fromhex(val["newpuzhash"])
assert bytes.fromhex(val["pubkey"])
filename = "test.attest"
val = await client.did_create_attest(
did_wallet_2.wallet_id, val["coin_name"], val["pubkey"], val["newpuzhash"], filename
)
if "success" in val:
assert val["success"]
for i in range(0, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
val = await client.did_recovery_spend(did_wallet_id_3, [filename])
if "success" in val:
assert val["success"]
for i in range(0, num_blocks * 2):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"\0"))
val = await client.get_wallet_balance(did_wallet_id_3)
assert val["confirmed_wallet_balance"] == 101
await rpc_server_cleanup()

View File

View File

@ -0,0 +1 @@
checkout_blocks_and_plots = True

View File

@ -0,0 +1,93 @@
from chia.types.blockchain_format.program import INFINITE_COST, Program
from chia.wallet.puzzles.load_clvm import load_clvm
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_for_pk, solution_for_conditions
from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition
from tests.core.make_block_generator import int_to_public_key
SINGLETON_MOD = load_clvm("singleton_top_layer.clvm")
LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
DID_MOD = load_clvm("did_innerpuz.clvm")
NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm")
STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm")
LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash()
NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
SINGLETON_MOD_HASH = SINGLETON_MOD.get_tree_hash()
LAUNCHER_ID = Program.to("launcher-id").get_tree_hash()
NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.clvm")
def test_new_nft_ownership_layer() -> None:
pubkey = int_to_public_key(1)
innerpuz = puzzle_for_pk(pubkey)
my_amount = 1
destination: Program = puzzle_for_pk(int_to_public_key(2))
condition_list = [make_create_coin_condition(destination.get_tree_hash(), my_amount, [])]
metadata = [
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
]
solution = Program.to(
[
NFT_STATE_LAYER_MOD_HASH,
metadata,
NFT_METADATA_UPDATER.get_tree_hash(),
innerpuz,
# below here is the solution
solution_for_conditions(condition_list),
my_amount,
0,
]
)
cost, res = NFT_STATE_LAYER_MOD.run_with_cost(INFINITE_COST, solution)
assert res.first().first().as_int() == 73
assert res.first().rest().first().as_int() == 1
assert res.rest().rest().first().first().as_int() == 51
assert (
res.rest().rest().first().rest().first().as_atom()
== NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination
).get_tree_hash()
)
def test_update_metadata() -> None:
pubkey = int_to_public_key(1)
innerpuz = puzzle_for_pk(pubkey)
my_amount = 1
destination: Program = puzzle_for_pk(int_to_public_key(2))
condition_list = [make_create_coin_condition(destination.get_tree_hash(), my_amount, [])]
condition_list.append([-24, NFT_METADATA_UPDATER, "https://www.chia.net/img/branding/chia-logo-2.svg"])
metadata = [
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
]
solution = Program.to(
[
NFT_STATE_LAYER_MOD_HASH,
metadata,
NFT_METADATA_UPDATER.get_tree_hash(),
innerpuz,
# below here is the solution
solution_for_conditions(condition_list),
my_amount,
0,
]
)
metadata = [
("u", ["https://www.chia.net/img/branding/chia-logo-2.svg", "https://www.chia.net/img/branding/chia-logo.svg"]),
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
]
cost, res = NFT_STATE_LAYER_MOD.run_with_cost(INFINITE_COST, solution)
assert res.first().first().as_int() == 73
assert res.first().rest().first().as_int() == 1
assert res.rest().rest().first().first().as_int() == 51
assert (
res.rest().rest().first().rest().first().as_atom()
== NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination
).get_tree_hash()
)

View File

@ -0,0 +1,468 @@
import asyncio
# pytestmark = pytest.mark.skip("TODO: Fix tests")
from typing import Any
import pytest
from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
from chia.full_node.mempool_manager import MempoolManager
from chia.rpc.wallet_rpc_api import WalletRpcApi
from chia.simulator.full_node_simulator import FullNodeSimulator
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.peer_info import PeerInfo
from chia.util.ints import uint16, uint32
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.util.compute_memos import compute_memos
from chia.wallet.util.wallet_types import WalletType
from tests.time_out_assert import time_out_assert, time_out_assert_not_none
async def tx_in_pool(mempool: MempoolManager, tx_id: bytes32) -> bool:
tx = mempool.get_spendbundle(tx_id)
if tx is None:
return False
return True
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_wallet_creation_automatically(two_wallet_nodes: Any, trusted: Any) -> None:
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
full_node_server = full_node_api.server
wallet_node_0, server_0 = wallets[0]
wallet_node_1, server_1 = wallets[1]
wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
ph = await wallet_0.get_new_puzzlehash()
ph1 = await wallet_1.get_new_puzzlehash()
if trusted:
wallet_node_0.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_1.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
else:
wallet_node_0.config["trusted_peers"] = {}
wallet_node_1.config["trusted_peers"] = {}
await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1)]
)
await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds)
await time_out_assert(10, wallet_0.get_confirmed_balance, funds)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, wallet_0.get_pending_change_balance, 0)
nft_wallet_0 = await NFTWallet.create_new_nft_wallet(
wallet_node_0.wallet_state_manager, wallet_0, name="NFT WALLET 1"
)
metadata = Program.to(
[
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F185"),
]
)
sb = await nft_wallet_0.generate_new_nft(metadata)
assert sb
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
await time_out_assert(5, len, 1, nft_wallet_0.nft_wallet_info.my_nft_coins)
coins = nft_wallet_0.nft_wallet_info.my_nft_coins
assert len(coins) == 1, "nft not generated"
sb = await nft_wallet_0.transfer_nft(coins[0], ph1)
assert sb is not None
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
assert len(wallet_node_1.wallet_state_manager.wallets) == 2
# Get the new NFT wallet
nft_wallets = await wallet_node_1.wallet_state_manager.get_all_wallet_info_entries(WalletType.NFT)
assert len(nft_wallets) == 1
nft_wallet_1: NFTWallet = wallet_node_1.wallet_state_manager.wallets[nft_wallets[0].id]
await time_out_assert(5, len, 1, nft_wallet_1.nft_wallet_info.my_nft_coins)
coins = nft_wallet_0.nft_wallet_info.my_nft_coins
assert len(coins) == 0
coins = nft_wallet_1.nft_wallet_info.my_nft_coins
assert len(coins) == 1
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_wallet_creation_and_transfer(two_wallet_nodes: Any, trusted: Any) -> None:
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api: FullNodeSimulator = full_nodes[0]
full_node_server = full_node_api.server
wallet_node_0, server_0 = wallets[0]
wallet_node_1, server_1 = wallets[1]
wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
ph = await wallet_0.get_new_puzzlehash()
ph1 = await wallet_1.get_new_puzzlehash()
if trusted:
wallet_node_0.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_1.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
else:
wallet_node_0.config["trusted_peers"] = {}
wallet_node_1.config["trusted_peers"] = {}
await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
funds = sum(
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1)]
)
await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds)
await time_out_assert(10, wallet_0.get_confirmed_balance, funds)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await time_out_assert(15, wallet_0.get_pending_change_balance, 0)
nft_wallet_0 = await NFTWallet.create_new_nft_wallet(
wallet_node_0.wallet_state_manager, wallet_0, name="NFT WALLET 1"
)
metadata = Program.to(
[
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F185"),
]
)
sb = await nft_wallet_0.generate_new_nft(metadata)
assert sb
# ensure hints are generated
assert compute_memos(sb)
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
coins = nft_wallet_0.nft_wallet_info.my_nft_coins
assert len(coins) == 1, "nft not generated"
metadata = Program.to(
[
("u", ["https://www.test.net/logo.svg"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F181"),
]
)
sb = await nft_wallet_0.generate_new_nft(metadata)
assert sb
# ensure hints are generated
assert compute_memos(sb)
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
await time_out_assert(15, len, 2, nft_wallet_0.nft_wallet_info.my_nft_coins)
coins = nft_wallet_0.nft_wallet_info.my_nft_coins
assert len(coins) == 2, "nft not generated"
nft_wallet_1 = await NFTWallet.create_new_nft_wallet(
wallet_node_1.wallet_state_manager, wallet_1, name="NFT WALLET 2"
)
sb = await nft_wallet_0.transfer_nft(coins[1], ph1)
assert sb is not None
# ensure hints are generated
assert compute_memos(sb)
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
coins = nft_wallet_0.nft_wallet_info.my_nft_coins
assert len(coins) == 1
coins = nft_wallet_1.nft_wallet_info.my_nft_coins
assert len(coins) == 1
# Send it back to original owner
nsb = await nft_wallet_1.transfer_nft(coins[0], ph)
assert nsb is not None
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, nsb.name())
# ensure hints are generated
assert compute_memos(nsb)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
await time_out_assert(5, len, 2, nft_wallet_0.nft_wallet_info.my_nft_coins)
await time_out_assert(5, len, 0, nft_wallet_1.nft_wallet_info.my_nft_coins)
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_wallet_rpc_creation_and_list(two_wallet_nodes: Any, trusted: Any) -> None:
num_blocks = 5
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
full_node_server = full_node_api.server
wallet_node_0, server_0 = wallets[0]
wallet_node_1, server_1 = wallets[1]
wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
ph = await wallet_0.get_new_puzzlehash()
_ = await wallet_1.get_new_puzzlehash()
if trusted:
wallet_node_0.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_1.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
else:
wallet_node_0.config["trusted_peers"] = {}
wallet_node_1.config["trusted_peers"] = {}
await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
api_0 = WalletRpcApi(wallet_node_0)
nft_wallet_0 = await api_0.create_new_wallet(dict(wallet_type="nft_wallet", name="NFT WALLET 1"))
assert isinstance(nft_wallet_0, dict)
assert nft_wallet_0.get("success")
nft_wallet_0_id = nft_wallet_0["wallet_id"]
tr1 = await api_0.nft_mint_nft(
{
"wallet_id": nft_wallet_0_id,
"artist_address": ph,
"hash": "0xD4584AD463139FA8C0D9F68F4B59F185",
"uris": ["https://www.chia.net/img/branding/chia-logo.svg"],
}
)
assert isinstance(tr1, dict)
assert tr1.get("success")
sb = tr1["spend_bundle"]
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
tr2 = await api_0.nft_mint_nft(
{
"wallet_id": nft_wallet_0_id,
"artist_address": ph,
"hash": "0xD4584AD463139FA8C0D9F68F4B59F184",
"uris": ["https://chialisp.com/img/logo.svg"],
}
)
assert isinstance(tr2, dict)
assert tr2.get("success")
sb = tr2["spend_bundle"]
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
time_left = 5.0
while time_left > 0:
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
try:
assert isinstance(coins_response, dict)
assert coins_response.get("success")
coins = coins_response["nft_list"]
assert len(coins) == 2
uris = []
for coin in coins:
uris.append(coin.to_json_dict()["data_uris"][0])
assert len(uris) == 2
assert "https://chialisp.com/img/logo.svg" in uris
assert bytes32.fromhex(coins[1].to_json_dict()["nft_coin_id"]) in [x.name() for x in sb.additions()]
except AssertionError:
if time_left < 0:
raise
await asyncio.sleep(0.5)
time_left -= 0.5
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_wallet_rpc_update_metadata(two_wallet_nodes: Any, trusted: Any) -> None:
num_blocks = 3
full_nodes, wallets = two_wallet_nodes
full_node_api = full_nodes[0]
full_node_server = full_node_api.server
wallet_node_0, server_0 = wallets[0]
wallet_node_1, server_1 = wallets[1]
wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
ph = await wallet_0.get_new_puzzlehash()
_ = await wallet_1.get_new_puzzlehash()
if trusted:
wallet_node_0.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
wallet_node_1.config["trusted_peers"] = {
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
}
else:
wallet_node_0.config["trusted_peers"] = {}
wallet_node_1.config["trusted_peers"] = {}
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
api_0 = WalletRpcApi(wallet_node_0)
await time_out_assert(10, wallet_node_0.wallet_state_manager.synced, True)
await time_out_assert(10, wallet_node_1.wallet_state_manager.synced, True)
nft_wallet_0 = await api_0.create_new_wallet(dict(wallet_type="nft_wallet", name="NFT WALLET 1"))
assert isinstance(nft_wallet_0, dict)
assert nft_wallet_0.get("success")
nft_wallet_0_id = nft_wallet_0["wallet_id"]
# mint NFT
resp = await api_0.nft_mint_nft(
{
"wallet_id": nft_wallet_0_id,
"artist_address": ph,
"hash": "0xD4584AD463139FA8C0D9F68F4B59F185",
"uris": ["https://www.chia.net/img/branding/chia-logo.svg"],
}
)
assert resp.get("success")
sb = resp["spend_bundle"]
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
time_left = 5.0
coins_response = {}
while time_left > 0:
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
if coins_response.get("nft_list"):
break
await asyncio.sleep(0.5)
time_left -= 0.5
assert coins_response["nft_list"], isinstance(coins_response, dict)
assert coins_response.get("success")
coins = coins_response["nft_list"]
coin = coins[0].to_json_dict()
nft_coin_id = coin["nft_coin_id"]
# add another URI
tr1 = await api_0.nft_add_uri(
{
"wallet_id": nft_wallet_0_id,
"nft_coin_id": nft_coin_id,
"hash": "0xD4584AD463139FA8C0D9F68F4B59F185",
"uri": "https://www.chia.net/img/branding/chia-logo-white.svg",
}
)
assert isinstance(tr1, dict)
assert tr1.get("success")
sb = tr1["spend_bundle"]
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
# check that new URI was added
time_left = 5.0
while time_left > 0:
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
try:
assert isinstance(coins_response, dict)
assert coins_response.get("success")
coins = coins_response["nft_list"]
assert len(coins) == 1
coin = coins[0].to_json_dict()
uris = coin["data_uris"]
assert len(uris) == 2
assert "https://www.chia.net/img/branding/chia-logo-white.svg" in uris
except AssertionError:
if time_left < 0:
raise
await asyncio.sleep(0.5)
time_left -= 0.5
# add yet another URI
nft_coin_id = coin["nft_coin_id"]
tr1 = await api_0.nft_add_uri(
{
"wallet_id": nft_wallet_0_id,
"nft_coin_id": nft_coin_id,
"hash": "0xD4584AD463139FA8C0D9F68F4B59F185",
"uri": "https://www.chia.net/img/branding/chia-logo-more-white.svg",
}
)
assert isinstance(tr1, dict)
assert tr1.get("success")
sb = tr1["spend_bundle"]
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
time_left = 5.0
while time_left > 0:
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
try:
assert isinstance(coins_response, dict)
assert coins_response.get("success")
coins = coins_response["nft_list"]
assert len(coins) == 1
coin = coins[0].to_json_dict()
uris = coin["data_uris"]
assert len(uris) == 3
assert "https://www.chia.net/img/branding/chia-logo-more-white.svg" in uris
except AssertionError:
if time_left < 0:
raise
await asyncio.sleep(0.5)
time_left -= 0.5

View File

@ -1,4 +1,5 @@
import dataclasses
import json
import logging
from operator import attrgetter
from typing import Any, Dict, List, Optional, Tuple
@ -31,6 +32,7 @@ from chia.util.ints import uint16, uint32, uint64
from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
from chia.wallet.cat_wallet.cat_wallet import CATWallet
from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.trading.trade_status import TradeStatus
from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.transaction_sorting import SortKey
@ -730,6 +732,96 @@ async def test_offer_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment)
assert len(all_offers) == 2
@pytest.mark.asyncio
async def test_did_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
env: WalletRpcTestEnvironment = wallet_rpc_environment
wallet_1: Wallet = env.wallet_1.wallet
wallet_2: Wallet = env.wallet_2.wallet
wallet_1_node: WalletNode = env.wallet_1.node
wallet_2_node: WalletNode = env.wallet_2.node
wallet_1_rpc: WalletRpcClient = env.wallet_1.rpc_client
full_node_api: FullNodeSimulator = env.full_node.api
await generate_funds(env.full_node.api, env.wallet_1, 5)
# Create a DID wallet
res = await wallet_1_rpc.create_new_did_wallet(amount=1, name=None)
assert res["success"]
did_wallet_id_0 = res["wallet_id"]
did_id_0 = res["my_did"]
# Get wallet name
res = await wallet_1_rpc.did_get_wallet_name(did_wallet_id_0)
assert res["success"]
assert res["name"] == "Profile 1"
# Set wallet name
new_wallet_name = "test name"
res = await wallet_1_rpc.did_set_wallet_name(did_wallet_id_0, new_wallet_name)
assert res["success"]
res = await wallet_1_rpc.did_get_wallet_name(did_wallet_id_0)
assert res["success"]
assert res["name"] == new_wallet_name
with pytest.raises(ValueError, match="Wallet id 1 is not a DID wallet"):
await wallet_1_rpc.did_set_wallet_name(wallet_1.id(), new_wallet_name)
# Check DID ID
res = await wallet_1_rpc.get_did_id(did_wallet_id_0)
assert res["success"]
assert did_id_0 == res["my_did"]
# Create backup file
res = await wallet_1_rpc.create_did_backup_file(did_wallet_id_0, "backup.did")
assert res["success"]
for _ in range(3):
await farm_transaction_block(full_node_api, wallet_1_node)
# Update recovery list
res = await wallet_1_rpc.update_did_recovery_list(did_wallet_id_0, [did_id_0], 1)
assert res["success"]
res = await wallet_1_rpc.get_did_recovery_list(did_wallet_id_0)
assert res["num_required"] == 1
assert res["recovery_list"][0] == did_id_0
for _ in range(3):
await farm_transaction_block(full_node_api, wallet_1_node)
# Update metadata
res = await wallet_1_rpc.update_did_metadata(did_wallet_id_0, {"Twitter": "Https://test"})
assert res["success"]
for _ in range(3):
await farm_transaction_block(full_node_api, wallet_1_node)
res = await wallet_1_rpc.get_did_metadata(did_wallet_id_0)
assert res["metadata"]["Twitter"] == "Https://test"
for _ in range(3):
await farm_transaction_block(full_node_api, wallet_1_node)
# Transfer DID
addr = encode_puzzle_hash(await wallet_2.get_new_puzzlehash(), "txch")
res = await wallet_1_rpc.did_transfer_did(did_wallet_id_0, addr, 0, True)
assert res["success"]
for _ in range(3):
await farm_transaction_block(full_node_api, wallet_1_node)
assert wallet_2_node.wallet_state_manager is not None
did_wallets = list(
filter(
lambda w: (w.type == WalletType.DISTRIBUTED_ID),
await wallet_2_node.wallet_state_manager.get_all_wallet_info_entries(),
)
)
did_wallet_2: DIDWallet = wallet_2_node.wallet_state_manager.wallets[did_wallets[0].id]
assert did_wallet_2.get_my_DID() == did_id_0
metadata = json.loads(did_wallet_2.did_info.metadata)
assert metadata["Twitter"] == "Https://test"
@pytest.mark.asyncio
async def test_key_and_address_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
env: WalletRpcTestEnvironment = wallet_rpc_environment