mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-11-10 12:29:49 +03:00
Merge commit 'd0de8038cd95b71fa050f79e3685c51dcf05e13e' into catchup/long_lived_atari_from_release_1.4.0_d0de8038cd95b71fa050f79e3685c51dcf05e13e
This commit is contained in:
commit
c429933737
@ -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: |
|
||||
|
117
.github/workflows/build-test-macos-wallet-nft_wallet.yml
vendored
Normal file
117
.github/workflows/build-test-macos-wallet-nft_wallet.yml
vendored
Normal 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
|
||||
#
|
@ -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: |
|
||||
|
119
.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml
vendored
Normal file
119
.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml
vendored
Normal 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
|
||||
#
|
10
.github/workflows/test-install-scripts.yml
vendored
10
.github/workflows/test-install-scripts.yml
vendored
@ -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: |
|
||||
|
@ -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.
|
||||
|
@ -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))
|
||||
|
@ -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}")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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(
|
||||
|
@ -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
@ -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
|
||||
|
0
chia/wallet/nft_wallet/__init__.py
Normal file
0
chia/wallet/nft_wallet/__init__.py
Normal file
50
chia/wallet/nft_wallet/nft_info.py
Normal file
50
chia/wallet/nft_wallet/nft_info.py
Normal file
@ -0,0 +1,50 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from chia.util.ints import uint64
|
||||
from chia.util.streamable import Streamable, streamable
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class NFTInfo(Streamable):
|
||||
"""NFT Info for displaying NFT on the UI"""
|
||||
|
||||
launcher_id: str
|
||||
"""Launcher coin ID"""
|
||||
|
||||
nft_coin_id: str
|
||||
"""Current NFT coin ID"""
|
||||
|
||||
did_owner: str
|
||||
"""Owner DID"""
|
||||
|
||||
royalty: uint64
|
||||
"""Percentage of the transaction fee paid to the author, e.g. 1000 = 1%"""
|
||||
|
||||
data_uris: List[str]
|
||||
""" A list of content URIs"""
|
||||
|
||||
data_hash: str
|
||||
"""Hash of the content"""
|
||||
|
||||
metadata_uris: List[str]
|
||||
"""A list of metadata URIs"""
|
||||
|
||||
metadata_hash: str
|
||||
"""Hash of the metadata"""
|
||||
|
||||
license_uris: List[str]
|
||||
"""A list of license URIs"""
|
||||
|
||||
license_hash: str
|
||||
"""Hash of the license"""
|
||||
|
||||
version: str
|
||||
"""Current NFT version"""
|
||||
|
||||
edition_count: uint64
|
||||
"""How many NFTs in the current series"""
|
||||
|
||||
edition_number: uint64
|
||||
"""Number of the current NFT in the series"""
|
101
chia/wallet/nft_wallet/nft_puzzles.py
Normal file
101
chia/wallet/nft_wallet/nft_puzzles.py
Normal file
@ -0,0 +1,101 @@
|
||||
import logging
|
||||
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.ints import uint64
|
||||
from chia.wallet.nft_wallet.nft_info import NFTInfo
|
||||
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
|
||||
LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
|
||||
NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm")
|
||||
LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash()
|
||||
SINGLETON_MOD_HASH = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
|
||||
NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
|
||||
NFT_TRANSFER_PROGRAM = load_clvm("nft_transfer_program.clvm")
|
||||
OFFER_MOD = load_clvm("settlement_payments.clvm")
|
||||
NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.clvm")
|
||||
|
||||
|
||||
def create_nft_layer_puzzle_with_curry_params(
|
||||
metadata: Program, metadata_updater_hash: bytes32, inner_puzzle: Program
|
||||
) -> Program:
|
||||
"""Curries params into nft_state_layer.clvm
|
||||
|
||||
Args to curry:
|
||||
NFT_STATE_LAYER_MOD_HASH
|
||||
METADATA
|
||||
METADATA_UPDATER_PUZZLE_HASH
|
||||
INNER_PUZZLE"""
|
||||
log.debug(
|
||||
"Creating nft layer puzzle curry: mod_hash: %s, metadata: %r, metadata_hash: %s",
|
||||
NFT_STATE_LAYER_MOD_HASH,
|
||||
metadata,
|
||||
metadata_updater_hash,
|
||||
)
|
||||
return NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, metadata_updater_hash, inner_puzzle)
|
||||
|
||||
|
||||
def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Program) -> Program:
|
||||
log.debug(
|
||||
"Creating full NFT puzzle with inner puzzle: \n%r\n%r",
|
||||
singleton_id,
|
||||
inner_puzzle.get_tree_hash(),
|
||||
)
|
||||
singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH)))
|
||||
|
||||
full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, inner_puzzle)
|
||||
log.debug("Created NFT full puzzle with inner: %s", full_puzzle.get_tree_hash())
|
||||
return full_puzzle
|
||||
|
||||
|
||||
def create_full_puzzle(
|
||||
singleton_id: bytes32, metadata: Program, metadata_updater_puzhash: bytes32, inner_puzzle: Program
|
||||
) -> Program:
|
||||
log.debug(
|
||||
"Creating full NFT puzzle with: \n%r\n%r\n%r\n%r",
|
||||
singleton_id,
|
||||
metadata.get_tree_hash(),
|
||||
metadata_updater_puzhash,
|
||||
inner_puzzle.get_tree_hash(),
|
||||
)
|
||||
singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH)))
|
||||
singleton_inner_puzzle = create_nft_layer_puzzle_with_curry_params(metadata, metadata_updater_puzhash, inner_puzzle)
|
||||
|
||||
full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, singleton_inner_puzzle)
|
||||
log.debug("Created NFT full puzzle: %s", full_puzzle.get_tree_hash())
|
||||
return full_puzzle
|
||||
|
||||
|
||||
def get_nft_info_from_puzzle(puzzle: Program, nft_coin: Coin) -> NFTInfo:
|
||||
"""
|
||||
Extract NFT info from a full puzzle
|
||||
:param puzzle: NFT full puzzle
|
||||
:param nft_coin: NFT coin
|
||||
:return: NFTInfo
|
||||
"""
|
||||
# TODO Update this method after the NFT code finalized
|
||||
uncurried_nft: UncurriedNFT = UncurriedNFT.uncurry(puzzle)
|
||||
data_uris = []
|
||||
for uri in uncurried_nft.data_uris.as_python():
|
||||
data_uris.append(str(uri, "utf-8"))
|
||||
|
||||
nft_info = NFTInfo(
|
||||
uncurried_nft.singleton_launcher_id.as_python().hex().upper(),
|
||||
nft_coin.name().hex().upper(),
|
||||
uncurried_nft.owner_did.as_python().hex().upper(),
|
||||
uint64(uncurried_nft.trade_price_percentage.as_int()),
|
||||
data_uris,
|
||||
uncurried_nft.data_hash.as_python().hex().upper(),
|
||||
[],
|
||||
"",
|
||||
[],
|
||||
"",
|
||||
"NFT0",
|
||||
uint64(1),
|
||||
uint64(1),
|
||||
)
|
||||
return nft_info
|
564
chia/wallet/nft_wallet/nft_wallet.py
Normal file
564
chia/wallet/nft_wallet/nft_wallet.py
Normal file
@ -0,0 +1,564 @@
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from secrets import token_bytes
|
||||
from typing import Any, Dict, List, Optional, Set, Type, TypeVar
|
||||
|
||||
from blspy import AugSchemeMPL, G1Element, G2Element
|
||||
from clvm.casts import int_from_bytes, int_to_bytes
|
||||
|
||||
from chia.clvm.singleton import SINGLETON_TOP_LAYER_MOD
|
||||
from chia.protocols.wallet_protocol import CoinState
|
||||
from chia.server.outbound_message import NodeType
|
||||
from chia.server.ws_connection import WSChiaConnection
|
||||
from chia.types.announcement import Announcement
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.types.spend_bundle import SpendBundle
|
||||
from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict
|
||||
from chia.util.ints import uint8, uint32, uint64, uint128
|
||||
from chia.util.streamable import Streamable, streamable
|
||||
from chia.wallet.derivation_record import DerivationRecord
|
||||
from chia.wallet.lineage_proof import LineageProof
|
||||
from chia.wallet.nft_wallet import nft_puzzles
|
||||
from chia.wallet.nft_wallet.nft_puzzles import LAUNCHER_PUZZLE, NFT_METADATA_UPDATER, NFT_STATE_LAYER_MOD_HASH
|
||||
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm
|
||||
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
|
||||
DEFAULT_HIDDEN_PUZZLE_HASH,
|
||||
calculate_synthetic_secret_key,
|
||||
puzzle_for_pk,
|
||||
solution_for_conditions,
|
||||
)
|
||||
from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.util.compute_memos import compute_memos
|
||||
from chia.wallet.util.debug_spend_bundle import disassemble
|
||||
from chia.wallet.util.transaction_type import TransactionType
|
||||
from chia.wallet.util.wallet_types import WalletType
|
||||
from chia.wallet.wallet import Wallet
|
||||
from chia.wallet.wallet_info import WalletInfo
|
||||
|
||||
_T_NFTWallet = TypeVar("_T_NFTWallet", bound="NFTWallet")
|
||||
|
||||
|
||||
OFFER_MOD = load_clvm("settlement_payments.clvm")
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class NFTCoinInfo(Streamable):
|
||||
coin: Coin
|
||||
lineage_proof: LineageProof
|
||||
full_puzzle: Program
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class NFTWalletInfo(Streamable):
|
||||
my_nft_coins: List[NFTCoinInfo]
|
||||
did_wallet_id: Optional[uint32] = None
|
||||
|
||||
|
||||
def create_fullpuz(innerpuz: Program, genesis_id: bytes32) -> Program:
|
||||
mod_hash = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
|
||||
# singleton_struct = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
|
||||
singleton_struct = Program.to((mod_hash, (genesis_id, LAUNCHER_PUZZLE.get_tree_hash())))
|
||||
return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, innerpuz)
|
||||
|
||||
|
||||
class NFTWallet:
|
||||
wallet_state_manager: Any
|
||||
log: logging.Logger
|
||||
wallet_info: WalletInfo
|
||||
nft_wallet_info: NFTWalletInfo
|
||||
standard_wallet: Wallet
|
||||
wallet_id: int
|
||||
|
||||
@classmethod
|
||||
async def create_new_nft_wallet(
|
||||
cls: Type[_T_NFTWallet],
|
||||
wallet_state_manager: Any,
|
||||
wallet: Wallet,
|
||||
did_wallet_id: uint32 = None,
|
||||
name: str = "",
|
||||
in_transaction: bool = False,
|
||||
) -> _T_NFTWallet:
|
||||
"""
|
||||
This must be called under the wallet state manager lock
|
||||
"""
|
||||
self = cls()
|
||||
self.standard_wallet = wallet
|
||||
self.log = logging.getLogger(name if name else __name__)
|
||||
self.wallet_state_manager = wallet_state_manager
|
||||
self.nft_wallet_info = NFTWalletInfo([], did_wallet_id)
|
||||
info_as_string = json.dumps(self.nft_wallet_info.to_json_dict())
|
||||
wallet_info = await wallet_state_manager.user_store.create_wallet(
|
||||
"NFT Wallet" if not name else name,
|
||||
uint32(WalletType.NFT.value),
|
||||
info_as_string,
|
||||
in_transaction=in_transaction,
|
||||
)
|
||||
|
||||
if wallet_info is None:
|
||||
raise ValueError("Internal Error")
|
||||
self.wallet_info = wallet_info
|
||||
self.wallet_id = self.wallet_info.id
|
||||
self.log.debug("NFT wallet id: %r and standard wallet id: %r", self.wallet_id, self.standard_wallet.wallet_id)
|
||||
|
||||
await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id, in_transaction=in_transaction)
|
||||
self.log.debug("Generated a new NFT wallet: %s", self.__dict__)
|
||||
if not did_wallet_id:
|
||||
# default profile wallet
|
||||
self.log.debug("Standard NFT wallet created")
|
||||
else:
|
||||
# TODO: handle DID wallet puzhash
|
||||
raise NotImplementedError()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
async def create(
|
||||
cls: Type[_T_NFTWallet],
|
||||
wallet_state_manager: Any,
|
||||
wallet: Wallet,
|
||||
wallet_info: WalletInfo,
|
||||
name: Optional[str] = None,
|
||||
) -> _T_NFTWallet:
|
||||
self = cls()
|
||||
self.log = logging.getLogger(name if name else __name__)
|
||||
self.wallet_state_manager = wallet_state_manager
|
||||
self.wallet_info = wallet_info
|
||||
self.wallet_id = wallet_info.id
|
||||
self.standard_wallet = wallet
|
||||
self.wallet_info = wallet_info
|
||||
self.nft_wallet_info = NFTWalletInfo.from_json_dict(json.loads(wallet_info.data))
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def type(cls) -> uint8:
|
||||
return uint8(WalletType.NFT)
|
||||
|
||||
async def get_new_puzzle(self) -> Program:
|
||||
self.log.debug("Getting new puzzle for NFT wallet: %s", self.id())
|
||||
return self.puzzle_for_pk((await self.wallet_state_manager.get_unused_derivation_record(self.id())).pubkey)
|
||||
|
||||
def id(self) -> uint32:
|
||||
return self.wallet_info.id
|
||||
|
||||
async def get_confirmed_balance(self, record_list=None) -> uint128:
|
||||
"""The NFT wallet doesn't really have a balance."""
|
||||
return uint128(0)
|
||||
|
||||
async def get_unconfirmed_balance(self, record_list=None) -> uint128:
|
||||
"""The NFT wallet doesn't really have a balance."""
|
||||
return uint128(0)
|
||||
|
||||
async def get_spendable_balance(self, unspent_records=None) -> uint128:
|
||||
"""The NFT wallet doesn't really have a balance."""
|
||||
return uint128(0)
|
||||
|
||||
async def get_pending_change_balance(self) -> uint64:
|
||||
return uint64(0)
|
||||
|
||||
async def get_max_send_amount(self, records=None):
|
||||
"""This is the confirmed balance, which we set to 0 as the NFT wallet doesn't have one."""
|
||||
return uint128(0)
|
||||
|
||||
def get_nft_coin_by_id(self, nft_coin_id: bytes32) -> NFTCoinInfo:
|
||||
for nft_coin in self.nft_wallet_info.my_nft_coins:
|
||||
if nft_coin.coin.name() == nft_coin_id:
|
||||
return nft_coin
|
||||
raise KeyError(f"Couldn't find coin with id: {nft_coin_id}")
|
||||
|
||||
async def add_nft_coin(self, coin: Coin, spent_height: uint32, in_transaction: bool) -> None:
|
||||
await self.coin_added(coin, spent_height, in_transaction=in_transaction)
|
||||
|
||||
async def coin_added(self, coin: Coin, height: uint32, in_transaction: bool) -> None:
|
||||
"""Notification from wallet state manager that wallet has been received."""
|
||||
self.log.info(f"NFT wallet %s has been notified that {coin} was added", self.wallet_info.name)
|
||||
for coin_info in self.nft_wallet_info.my_nft_coins:
|
||||
if coin_info.coin == coin:
|
||||
return
|
||||
wallet_node = self.wallet_state_manager.wallet_node
|
||||
server = wallet_node.server
|
||||
full_nodes: Dict[bytes32, WSChiaConnection] = server.connection_by_type.get(NodeType.FULL_NODE, {})
|
||||
cs: Optional[CoinSpend] = None
|
||||
coin_states: Optional[List[CoinState]] = await self.wallet_state_manager.wallet_node.get_coin_state(
|
||||
[coin.parent_coin_info]
|
||||
)
|
||||
if not coin_states:
|
||||
# farm coin
|
||||
return
|
||||
assert coin_states
|
||||
parent_coin = coin_states[0].coin
|
||||
for node_id in full_nodes:
|
||||
node = server.all_connections[node_id]
|
||||
cs = await wallet_node.fetch_puzzle_solution(node, height, parent_coin)
|
||||
if cs is not None:
|
||||
break
|
||||
assert cs is not None
|
||||
await self.puzzle_solution_received(cs, in_transaction=in_transaction)
|
||||
|
||||
async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: bool) -> None:
|
||||
self.log.debug("Puzzle solution received to wallet: %s", self.wallet_info)
|
||||
coin_name = coin_spend.coin.name()
|
||||
puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal))
|
||||
solution: Program = Program.from_bytes(bytes(coin_spend.solution)).rest().rest().first().first()
|
||||
# At this point, the puzzle must be a NFT puzzle.
|
||||
# This method will be called only when the walle state manager uncurried this coin as a NFT puzzle.
|
||||
|
||||
uncurried_nft = UncurriedNFT.uncurry(puzzle)
|
||||
self.log.info(
|
||||
f"found the info for NFT coin {coin_name} {uncurried_nft.inner_puzzle} {uncurried_nft.singleton_struct}"
|
||||
)
|
||||
singleton_id = bytes32(uncurried_nft.singleton_launcher_id.atom)
|
||||
metadata = uncurried_nft.metadata
|
||||
new_inner_puzzle = None
|
||||
parent_inner_puzhash = uncurried_nft.nft_state_layer.get_tree_hash()
|
||||
self.log.debug("Before spend metadata: %s %s \n%s", metadata, singleton_id, disassemble(solution))
|
||||
for condition in solution.rest().first().rest().as_iter():
|
||||
self.log.debug("Checking solution condition: %s", disassemble(condition))
|
||||
if condition.list_len() < 2:
|
||||
# invalid condition
|
||||
continue
|
||||
condition_code = int_from_bytes(condition.first().atom)
|
||||
self.log.debug("Checking condition code: %r", condition_code)
|
||||
if condition_code == -24:
|
||||
# metadata update
|
||||
# (-24 (meta updater puzzle) url)
|
||||
metadata_list = list(metadata.as_python())
|
||||
new_metadata = []
|
||||
for metadata_entry in metadata_list:
|
||||
key = metadata_entry[0]
|
||||
if key == b"u":
|
||||
new_metadata.append((b"u", [condition.rest().rest().first().atom] + list(metadata_entry[1:])))
|
||||
else:
|
||||
new_metadata.append((b"h", metadata_entry[1]))
|
||||
metadata = Program.to(new_metadata)
|
||||
elif condition_code == 51 and int_from_bytes(condition.rest().rest().first().atom) == 1:
|
||||
puzhash = bytes32(condition.rest().first().atom)
|
||||
self.log.debug("Got back puzhash from solution: %s", puzhash)
|
||||
derivation_record: Optional[
|
||||
DerivationRecord
|
||||
] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(puzhash)
|
||||
if derivation_record is None:
|
||||
# we potentially sent it somewhere
|
||||
await self.remove_coin(coin_spend.coin, in_transaction=in_transaction)
|
||||
return
|
||||
new_inner_puzzle = puzzle_for_pk(derivation_record.pubkey)
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid condition")
|
||||
if new_inner_puzzle is None:
|
||||
raise ValueError("Invalid puzzle")
|
||||
parent_coin = None
|
||||
coin_record = await self.wallet_state_manager.coin_store.get_coin_record(coin_name)
|
||||
if coin_record is None:
|
||||
coin_states: Optional[List[CoinState]] = await self.wallet_state_manager.wallet_node.get_coin_state(
|
||||
[coin_name]
|
||||
)
|
||||
if coin_states is not None:
|
||||
parent_coin = coin_states[0].coin
|
||||
if coin_record is not None:
|
||||
parent_coin = coin_record.coin
|
||||
if parent_coin is None:
|
||||
raise ValueError("Error in finding parent")
|
||||
self.log.debug("Got back updated metadata: %s", metadata)
|
||||
child_puzzle: Program = nft_puzzles.create_full_puzzle(
|
||||
singleton_id,
|
||||
metadata,
|
||||
bytes32(uncurried_nft.metdata_updater_hash.atom),
|
||||
new_inner_puzzle,
|
||||
)
|
||||
self.log.debug(
|
||||
"Created NFT full puzzle with inner: %s",
|
||||
nft_puzzles.create_full_puzzle_with_nft_puzzle(singleton_id, uncurried_nft.inner_puzzle),
|
||||
)
|
||||
child_coin: Optional[Coin] = None
|
||||
for new_coin in coin_spend.additions():
|
||||
self.log.debug(
|
||||
"Comparing addition: %s with %s, amount: %s ",
|
||||
new_coin.puzzle_hash,
|
||||
child_puzzle.get_tree_hash(),
|
||||
new_coin.amount,
|
||||
)
|
||||
if new_coin.puzzle_hash == child_puzzle.get_tree_hash():
|
||||
child_coin = new_coin
|
||||
break
|
||||
else:
|
||||
raise ValueError("Invalid NFT spend on %r" % coin_name)
|
||||
self.log.info("Adding a new NFT to wallet: %s", child_coin)
|
||||
await self.add_coin(
|
||||
child_coin,
|
||||
child_puzzle,
|
||||
LineageProof(parent_coin.parent_coin_info, parent_inner_puzhash, parent_coin.amount),
|
||||
in_transaction=in_transaction,
|
||||
)
|
||||
|
||||
async def add_coin(self, coin: Coin, puzzle: Program, lineage_proof: LineageProof, in_transaction: bool) -> None:
|
||||
my_nft_coins = self.nft_wallet_info.my_nft_coins
|
||||
for coin_info in my_nft_coins:
|
||||
if coin_info.coin == coin:
|
||||
my_nft_coins.remove(coin_info)
|
||||
|
||||
my_nft_coins.append(NFTCoinInfo(coin, lineage_proof, puzzle))
|
||||
new_nft_wallet_info = NFTWalletInfo(
|
||||
my_nft_coins,
|
||||
self.nft_wallet_info.did_wallet_id,
|
||||
)
|
||||
await self.save_info(new_nft_wallet_info, in_transaction=in_transaction)
|
||||
await self.wallet_state_manager.add_interested_coin_ids([coin.name()], in_transaction=in_transaction)
|
||||
self.wallet_state_manager.state_changed("nft_coin_added", self.wallet_info.id)
|
||||
return
|
||||
|
||||
async def remove_coin(self, coin: Coin, in_transaction: bool) -> None:
|
||||
my_nft_coins = self.nft_wallet_info.my_nft_coins
|
||||
for coin_info in my_nft_coins:
|
||||
if coin_info.coin == coin:
|
||||
my_nft_coins.remove(coin_info)
|
||||
new_nft_wallet_info = NFTWalletInfo(
|
||||
my_nft_coins,
|
||||
self.nft_wallet_info.did_wallet_id,
|
||||
)
|
||||
await self.save_info(new_nft_wallet_info, in_transaction=in_transaction)
|
||||
self.wallet_state_manager.state_changed("nft_coin_removed", self.wallet_info.id)
|
||||
return
|
||||
|
||||
def puzzle_for_pk(self, pk: G1Element) -> Program:
|
||||
if not self.nft_wallet_info.did_wallet_id:
|
||||
inner_puzzle = self.standard_wallet.puzzle_for_pk(bytes(pk))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
provenance_puzzle = Program.to([NFT_STATE_LAYER_MOD_HASH, inner_puzzle])
|
||||
self.log.debug(
|
||||
"Wallet name %s generated a puzzle: %s", self.wallet_info.name, provenance_puzzle.get_tree_hash()
|
||||
)
|
||||
return provenance_puzzle
|
||||
|
||||
async def generate_new_nft(
|
||||
self, metadata: Program, target_puzzle_hash: bytes32 = None, fee: uint64 = uint64(0)
|
||||
) -> Optional[SpendBundle]:
|
||||
"""
|
||||
This must be called under the wallet state manager lock
|
||||
"""
|
||||
amount = uint64(1)
|
||||
coins = await self.standard_wallet.select_coins(amount)
|
||||
if coins is None:
|
||||
return None
|
||||
self.log.debug("Attempt to generate a new NFT")
|
||||
origin = coins.copy().pop()
|
||||
genesis_launcher_puz = nft_puzzles.LAUNCHER_PUZZLE
|
||||
launcher_coin = Coin(origin.name(), genesis_launcher_puz.get_tree_hash(), uint64(amount))
|
||||
self.log.debug("Generating NFT with launcher coin %s and metadata: %s", launcher_coin, metadata)
|
||||
|
||||
inner_puzzle = await self.standard_wallet.get_new_puzzle()
|
||||
# singleton eve
|
||||
eve_fullpuz = nft_puzzles.create_full_puzzle(
|
||||
launcher_coin.name(), metadata, NFT_METADATA_UPDATER.get_tree_hash(), inner_puzzle
|
||||
)
|
||||
# launcher announcement
|
||||
announcement_set: Set[Announcement] = set()
|
||||
announcement_message = Program.to([eve_fullpuz.get_tree_hash(), amount, []]).get_tree_hash()
|
||||
announcement_set.add(Announcement(launcher_coin.name(), announcement_message))
|
||||
|
||||
self.log.debug(
|
||||
"Creating transaction for launcher: %s and other coins: %s (%s)", origin, coins, announcement_set
|
||||
)
|
||||
# store the launcher transaction in the wallet state
|
||||
tx_record: Optional[TransactionRecord] = await self.standard_wallet.generate_signed_transaction(
|
||||
uint64(amount),
|
||||
genesis_launcher_puz.get_tree_hash(),
|
||||
fee,
|
||||
origin.name(),
|
||||
coins,
|
||||
None,
|
||||
False,
|
||||
announcement_set,
|
||||
)
|
||||
|
||||
genesis_launcher_solution = Program.to([eve_fullpuz.get_tree_hash(), amount, []])
|
||||
|
||||
# launcher spend to generate the singleton
|
||||
launcher_cs = CoinSpend(launcher_coin, genesis_launcher_puz, genesis_launcher_solution)
|
||||
launcher_sb = SpendBundle([launcher_cs], AugSchemeMPL.aggregate([]))
|
||||
|
||||
eve_coin = Coin(launcher_coin.name(), eve_fullpuz.get_tree_hash(), uint64(amount))
|
||||
|
||||
if tx_record is None or tx_record.spend_bundle is None:
|
||||
return None
|
||||
|
||||
if not target_puzzle_hash:
|
||||
target_puzzle_hash = inner_puzzle.get_tree_hash()
|
||||
condition_list = [make_create_coin_condition(target_puzzle_hash, amount, [target_puzzle_hash])]
|
||||
innersol = solution_for_conditions(condition_list)
|
||||
# EVE SPEND BELOW
|
||||
fullsol = Program.to(
|
||||
[
|
||||
[launcher_coin.parent_coin_info, launcher_coin.amount],
|
||||
eve_coin.amount,
|
||||
Program.to(
|
||||
[
|
||||
innersol,
|
||||
amount,
|
||||
0,
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
list_of_coinspends = [CoinSpend(eve_coin, eve_fullpuz, fullsol)]
|
||||
eve_spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([]))
|
||||
eve_spend_bundle = await self.sign(eve_spend_bundle)
|
||||
full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend_bundle, launcher_sb])
|
||||
|
||||
nft_record = TransactionRecord(
|
||||
confirmed_at_height=uint32(0),
|
||||
created_at_time=uint64(int(time.time())),
|
||||
to_puzzle_hash=eve_fullpuz.get_tree_hash(),
|
||||
amount=uint64(amount),
|
||||
fee_amount=fee,
|
||||
confirmed=False,
|
||||
sent=uint32(0),
|
||||
spend_bundle=full_spend,
|
||||
additions=full_spend.additions(),
|
||||
removals=full_spend.removals(),
|
||||
wallet_id=self.wallet_info.id,
|
||||
sent_to=[],
|
||||
trade_id=None,
|
||||
type=uint32(TransactionType.OUTGOING_TX.value),
|
||||
name=bytes32(token_bytes()),
|
||||
memos=list(compute_memos(full_spend).items()),
|
||||
)
|
||||
await self.standard_wallet.push_transaction(nft_record)
|
||||
return nft_record.spend_bundle
|
||||
|
||||
async def sign(self, spend_bundle: SpendBundle) -> SpendBundle:
|
||||
sigs: List[G2Element] = []
|
||||
for spend in spend_bundle.coin_spends:
|
||||
try:
|
||||
uncurried_nft = UncurriedNFT.uncurry(spend.puzzle_reveal.to_program())
|
||||
except Exception as e:
|
||||
# This only happens while you changed the NFT chialisp but didn't update the UncurriedNFT class.
|
||||
self.log.error(e)
|
||||
else:
|
||||
self.log.debug("Found a NFT state layer to sign")
|
||||
puzzle_hash = uncurried_nft.inner_puzzle.get_tree_hash()
|
||||
keys = await self.wallet_state_manager.get_keys(puzzle_hash)
|
||||
assert keys
|
||||
_, private = keys
|
||||
synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH)
|
||||
error, conditions, cost = conditions_dict_for_solution(
|
||||
spend.puzzle_reveal.to_program(),
|
||||
spend.solution.to_program(),
|
||||
self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM,
|
||||
)
|
||||
if conditions is not None:
|
||||
synthetic_pk = synthetic_secret_key.get_g1()
|
||||
for pk, msg in pkm_pairs_for_conditions_dict(
|
||||
conditions, spend.coin.name(), self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
|
||||
):
|
||||
try:
|
||||
assert bytes(synthetic_pk) == pk
|
||||
sigs.append(AugSchemeMPL.sign(synthetic_secret_key, msg))
|
||||
except AssertionError:
|
||||
raise ValueError("This spend bundle cannot be signed by the NFT wallet")
|
||||
|
||||
agg_sig = AugSchemeMPL.aggregate(sigs)
|
||||
return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)])
|
||||
|
||||
async def _make_nft_transaction(
|
||||
self, nft_coin_info: NFTCoinInfo, inner_solution: Program, fee: uint64 = uint64(0)
|
||||
) -> TransactionRecord:
|
||||
|
||||
coin = nft_coin_info.coin
|
||||
amount = coin.amount
|
||||
full_puzzle = nft_coin_info.full_puzzle
|
||||
lineage_proof = nft_coin_info.lineage_proof
|
||||
self.log.debug("Inner solution: %r", disassemble(inner_solution))
|
||||
full_solution = Program.to(
|
||||
[
|
||||
[lineage_proof.parent_name, lineage_proof.inner_puzzle_hash, lineage_proof.amount],
|
||||
coin.amount,
|
||||
Program.to(
|
||||
[
|
||||
inner_solution,
|
||||
amount,
|
||||
0,
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
list_of_coinspends = [CoinSpend(coin, full_puzzle.to_serialized_program(), full_solution)]
|
||||
spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([]))
|
||||
spend_bundle = await self.sign(spend_bundle)
|
||||
full_spend = SpendBundle.aggregate([spend_bundle])
|
||||
self.log.debug("Memos are: %r", list(compute_memos(full_spend).items()))
|
||||
nft_record = TransactionRecord(
|
||||
confirmed_at_height=uint32(0),
|
||||
created_at_time=uint64(int(time.time())),
|
||||
to_puzzle_hash=full_puzzle.get_tree_hash(),
|
||||
amount=uint64(amount),
|
||||
fee_amount=fee,
|
||||
confirmed=False,
|
||||
sent=uint32(0),
|
||||
spend_bundle=full_spend,
|
||||
additions=full_spend.additions(),
|
||||
removals=full_spend.removals(),
|
||||
wallet_id=self.wallet_info.id,
|
||||
sent_to=[],
|
||||
trade_id=None,
|
||||
type=uint32(TransactionType.OUTGOING_TX.value),
|
||||
name=bytes32(token_bytes()),
|
||||
memos=list(compute_memos(full_spend).items()),
|
||||
)
|
||||
return nft_record
|
||||
|
||||
async def update_metadata(
|
||||
self, nft_coin_info: NFTCoinInfo, uri: str, fee: uint64 = uint64(0)
|
||||
) -> Optional[SpendBundle]:
|
||||
coin = nft_coin_info.coin
|
||||
# we're not changing it
|
||||
|
||||
uncurried_nft = UncurriedNFT.uncurry(nft_coin_info.full_puzzle)
|
||||
|
||||
puzzle_hash = uncurried_nft.inner_puzzle.get_tree_hash()
|
||||
condition_list = [make_create_coin_condition(puzzle_hash, coin.amount, [puzzle_hash])]
|
||||
condition_list.append([int_to_bytes(-24), NFT_METADATA_UPDATER, uri.encode("utf-8")])
|
||||
|
||||
self.log.info("Attempting to add a url to NFT coin %s in the metadata: %s", nft_coin_info, uri)
|
||||
inner_solution = solution_for_conditions(condition_list)
|
||||
nft_tx_record = await self._make_nft_transaction(nft_coin_info, inner_solution, fee)
|
||||
await self.standard_wallet.push_transaction(nft_tx_record)
|
||||
return nft_tx_record.spend_bundle
|
||||
|
||||
async def transfer_nft(
|
||||
self,
|
||||
nft_coin_info: NFTCoinInfo,
|
||||
puzzle_hash: bytes32,
|
||||
did_hash=None,
|
||||
fee: uint64 = uint64(0),
|
||||
) -> Optional[SpendBundle]:
|
||||
self.log.debug("Attempt to transfer a new NFT")
|
||||
coin = nft_coin_info.coin
|
||||
self.log.debug("Transfering NFT coin %r to puzhash: %s", nft_coin_info, puzzle_hash)
|
||||
|
||||
amount = coin.amount
|
||||
condition_list = [make_create_coin_condition(puzzle_hash, amount, [bytes32(puzzle_hash)])]
|
||||
self.log.debug("Condition for new coin: %r", condition_list)
|
||||
inner_solution = solution_for_conditions(condition_list)
|
||||
nft_tx_record = await self._make_nft_transaction(nft_coin_info, inner_solution, fee)
|
||||
await self.standard_wallet.push_transaction(nft_tx_record)
|
||||
return nft_tx_record.spend_bundle
|
||||
|
||||
def get_current_nfts(self) -> List[NFTCoinInfo]:
|
||||
return self.nft_wallet_info.my_nft_coins
|
||||
|
||||
async def save_info(self, nft_info: NFTWalletInfo, in_transaction: bool) -> None:
|
||||
self.nft_wallet_info = nft_info
|
||||
current_info = self.wallet_info
|
||||
data_str = json.dumps(nft_info.to_json_dict())
|
||||
wallet_info = WalletInfo(current_info.id, current_info.name, current_info.type, data_str)
|
||||
self.wallet_info = wallet_info
|
||||
await self.wallet_state_manager.user_store.update_wallet(wallet_info, in_transaction)
|
122
chia/wallet/nft_wallet/uncurry_nft.py
Normal file
122
chia/wallet/nft_wallet/uncurry_nft.py
Normal file
@ -0,0 +1,122 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Type, TypeVar
|
||||
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm
|
||||
|
||||
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
|
||||
NFT_MOD = load_clvm("nft_state_layer.clvm")
|
||||
|
||||
_T_UncurriedNFT = TypeVar("_T_UncurriedNFT", bound="UncurriedNFT")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class UncurriedNFT:
|
||||
"""
|
||||
A simple solution for uncurry NFT puzzle.
|
||||
Initial the class with a full NFT puzzle, it will do a deep uncurry.
|
||||
This is the only place you need to change after modified the Chialisp curried parameters.
|
||||
"""
|
||||
|
||||
nft_mod_hash: Program
|
||||
"""NFT module hash"""
|
||||
|
||||
nft_state_layer: Program
|
||||
"""NFT state layer puzzle"""
|
||||
|
||||
singleton_struct: Program
|
||||
"""
|
||||
Singleton struct
|
||||
[singleton_mod_hash, singleton_launcher_id, launcher_puzhash]
|
||||
"""
|
||||
singleton_mod_hash: Program
|
||||
singleton_launcher_id: Program
|
||||
launcher_puzhash: Program
|
||||
|
||||
owner_did: Program
|
||||
"""Owner's DID"""
|
||||
|
||||
metdata_updater_hash: Program
|
||||
"""Metadata updater puzzle hash"""
|
||||
|
||||
transfer_program_hash: Program
|
||||
"""Puzzle hash of the transfer program"""
|
||||
|
||||
transfer_program_curry_params: Program
|
||||
"""
|
||||
Curried parameters of the transfer program
|
||||
[royalty_address, trade_price_percentage, settlement_mod_hash, cat_mod_hash]
|
||||
"""
|
||||
royalty_address: Program
|
||||
trade_price_percentage: Program
|
||||
settlement_mod_hash: Program
|
||||
cat_mod_hash: Program
|
||||
|
||||
metadata: Program
|
||||
"""
|
||||
NFT metadata
|
||||
[("u", data_uris), ("h", data_hash)]
|
||||
"""
|
||||
data_uris: Program
|
||||
data_hash: Program
|
||||
|
||||
inner_puzzle: Program
|
||||
"""NFT state layer inner puzzle"""
|
||||
|
||||
@classmethod
|
||||
def uncurry(cls: Type[_T_UncurriedNFT], puzzle: Program) -> UncurriedNFT:
|
||||
"""
|
||||
Try to uncurry a NFT puzzle
|
||||
:param cls UncurriedNFT class
|
||||
:param puzzle: Puzzle program
|
||||
:return Uncurried NFT
|
||||
"""
|
||||
mod, curried_args = puzzle.uncurry()
|
||||
if mod != SINGLETON_TOP_LAYER_MOD:
|
||||
raise ValueError(f"Cannot uncurry NFT puzzle, failed on singleton top layer: Mod {mod}")
|
||||
try:
|
||||
(singleton_struct, nft_state_layer) = curried_args.as_iter()
|
||||
singleton_mod_hash = singleton_struct.first()
|
||||
singleton_launcher_id = singleton_struct.rest().first()
|
||||
launcher_puzhash = singleton_struct.rest().rest()
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Cannot uncurry singleton top layer: Args {curried_args}") from e
|
||||
|
||||
mod, curried_args = curried_args.rest().first().uncurry()
|
||||
if mod != NFT_MOD:
|
||||
raise ValueError(f"Cannot uncurry NFT puzzle, failed on NFT state layer: Mod {mod}")
|
||||
try:
|
||||
# Set nft parameters
|
||||
(nft_mod_hash, metadata, metdata_updater_hash, inner_puzzle) = curried_args.as_iter()
|
||||
|
||||
# Set metadata
|
||||
for kv_pair in metadata.as_iter():
|
||||
if kv_pair.first().as_atom() == b"u":
|
||||
data_uris = kv_pair.rest()
|
||||
if kv_pair.first().as_atom() == b"h":
|
||||
data_hash = kv_pair.rest()
|
||||
except Exception as e:
|
||||
raise ValueError(f"Cannot uncurry NFT state layer: Args {curried_args}") from e
|
||||
return cls(
|
||||
nft_mod_hash=nft_mod_hash,
|
||||
nft_state_layer=nft_state_layer,
|
||||
singleton_struct=singleton_struct,
|
||||
singleton_mod_hash=singleton_mod_hash,
|
||||
singleton_launcher_id=singleton_launcher_id,
|
||||
launcher_puzhash=launcher_puzhash,
|
||||
metadata=metadata,
|
||||
data_uris=data_uris,
|
||||
data_hash=data_hash,
|
||||
metdata_updater_hash=metdata_updater_hash,
|
||||
inner_puzzle=inner_puzzle,
|
||||
# TODO Set/Remove following fields after NFT1 implemented
|
||||
owner_did=Program.to([]),
|
||||
transfer_program_hash=Program.to([]),
|
||||
transfer_program_curry_params=Program.to([]),
|
||||
royalty_address=Program.to([]),
|
||||
trade_price_percentage=Program.to([]),
|
||||
settlement_mod_hash=Program.to([]),
|
||||
cat_mod_hash=Program.to([]),
|
||||
)
|
@ -1 +1 @@
|
||||
ff02ffff01ff02ff5effff04ff02ffff04ffff04ff05ffff04ffff0bff2cff0580ffff04ff0bff80808080ffff04ffff02ff17ff2f80ffff04ff5fffff04ffff02ff2effff04ff02ffff04ff17ff80808080ffff04ffff0bff82027fff82057fff820b7f80ffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff820bffff80808080808080808080808080ffff04ffff01ffffffff81ca3dff46ff0233ffff3c04ff01ff0181cbffffff02ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff2cff3480ffff0bff22ffff0bff22ffff0bff2cff5c80ff0980ffff0bff22ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff0bffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff13ff80808080ff820b9f80ffff01ff02ff26ffff04ff02ffff04ffff02ff13ffff04ff5fffff04ff17ffff04ff2fffff04ff81bfffff04ff82017fffff04ff1bff8080808080808080ffff04ff82017fff8080808080ffff01ff088080ff0180ffff01ff02ffff03ff17ffff01ff02ffff03ffff20ff81bf80ffff0182017fffff01ff088080ff0180ffff01ff088080ff018080ff0180ffff04ffff04ff05ff2780ffff04ffff10ff0bff5780ff778080ff02ffff03ff05ffff01ff02ffff03ffff09ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff01818f80ffff01ff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ffff04ff81b9ff82017980ff808080808080ffff01ff02ff5affff04ff02ffff04ffff02ffff03ffff09ff11ff7880ffff01ff04ff78ffff04ffff02ff36ffff04ff02ffff04ff13ffff04ff29ffff04ffff0bff2cff5b80ffff04ff2bff80808080808080ff398080ffff01ff02ffff03ffff09ff11ff2480ffff01ff04ff24ffff04ffff0bff20ff2980ff398080ffff010980ff018080ff0180ffff04ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff04ffff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ff17ff808080808080ff80808080808080ff0180ffff01ff04ff80ffff04ff80ff17808080ff0180ffffff02ffff03ff05ffff01ff04ff09ffff02ff26ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff0bff22ffff0bff2cff5880ffff0bff22ffff0bff22ffff0bff2cff5c80ff0580ffff0bff22ffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff2cff058080ff0180ffff04ffff04ff28ffff04ff5fff808080ffff02ff7effff04ff02ffff04ffff04ffff04ff2fff0580ffff04ff5fff82017f8080ffff04ffff02ff7affff04ff02ffff04ff0bffff04ff05ffff01ff808080808080ffff04ff17ffff04ff81bfffff04ff82017fffff04ffff0bff8204ffffff02ff36ffff04ff02ffff04ff09ffff04ff820affffff04ffff0bff2cff2d80ffff04ff15ff80808080808080ff8216ff80ffff04ff8205ffffff04ff820bffff808080808080808080808080ff02ff2affff04ff02ffff04ff5fffff04ff3bffff04ffff02ffff03ff17ffff01ff09ff2dffff0bff27ffff02ff36ffff04ff02ffff04ff29ffff04ff57ffff04ffff0bff2cff81b980ffff04ff59ff80808080808080ff81b78080ff8080ff0180ffff04ff17ffff04ff05ffff04ff8202ffffff04ffff04ffff04ff24ffff04ffff0bff7cff2fff82017f80ff808080ffff04ffff04ff30ffff04ffff0bff81bfffff0bff7cff15ffff10ff82017fffff11ff8202dfff2b80ff8202ff808080ff808080ff138080ff80808080808080808080ff018080
|
||||
ff02ffff01ff02ff5effff04ff02ffff04ffff04ff05ffff04ffff0bff2cff0580ffff04ff0bff80808080ffff04ffff02ff17ff2f80ffff04ff5fffff04ffff02ff2effff04ff02ffff04ff17ff80808080ffff04ffff0bff82027fff82057fff820b7f80ffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff820bffff80808080808080808080808080ffff04ffff01ffffffff81ca3dff46ff0233ffff3c04ff01ff0181cbffffff02ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff2cff3480ffff0bff22ffff0bff22ffff0bff2cff5c80ff0980ffff0bff22ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff0bffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff13ff80808080ff820b9f80ffff01ff02ff26ffff04ff02ffff04ffff02ff13ffff04ff5fffff04ff17ffff04ff2fffff04ff81bfffff04ff82017fffff04ff1bff8080808080808080ffff04ff82017fff8080808080ffff01ff088080ff0180ffff01ff02ffff03ff17ffff01ff02ffff03ffff20ff81bf80ffff0182017fffff01ff088080ff0180ffff01ff088080ff018080ff0180ffff04ffff04ff05ff2780ffff04ffff10ff0bff5780ff778080ff02ffff03ff05ffff01ff02ffff03ffff09ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff01818f80ffff01ff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ffff04ff81b9ff82017980ff808080808080ffff01ff02ff5affff04ff02ffff04ffff02ffff03ffff09ff11ff7880ffff01ff04ff78ffff04ffff02ff36ffff04ff02ffff04ff13ffff04ff29ffff04ffff0bff2cff5b80ffff04ff2bff80808080808080ff398080ffff01ff02ffff03ffff09ff11ff2480ffff01ff04ff24ffff04ffff0bff20ff2980ff398080ffff010980ff018080ff0180ffff04ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff04ffff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ff17ff808080808080ff80808080808080ff0180ffff01ff04ff80ffff04ff80ff17808080ff0180ffffff02ffff03ff05ffff01ff04ff09ffff02ff26ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff0bff22ffff0bff2cff5880ffff0bff22ffff0bff22ffff0bff2cff5c80ff0580ffff0bff22ffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff2cff058080ff0180ffff04ffff04ff28ffff04ff5fff808080ffff02ff7effff04ff02ffff04ffff04ffff04ff2fff0580ffff04ff5fff82017f8080ffff04ffff02ff7affff04ff02ffff04ff0bffff04ff05ffff01ff808080808080ffff04ff17ffff04ff81bfffff04ff82017fffff04ffff0bff8204ffffff02ff36ffff04ff02ffff04ff09ffff04ff820affffff04ffff0bff2cff2d80ffff04ff15ff80808080808080ff8216ff80ffff04ff8205ffffff04ff820bffff808080808080808080808080ff02ff2affff04ff02ffff04ff5fffff04ff3bffff04ffff02ffff03ff17ffff01ff09ff2dffff0bff27ffff02ff36ffff04ff02ffff04ff29ffff04ff57ffff04ffff0bff2cff81b980ffff04ff59ff80808080808080ff81b78080ff8080ff0180ffff04ff17ffff04ff05ffff04ff8202ffffff04ffff04ffff04ff24ffff04ffff0bff7cff2fff82017f80ff808080ffff04ffff04ff30ffff04ffff0bff81bfffff0bff7cff15ffff10ff82017fffff11ff8202dfff2b80ff8202ff808080ff808080ff138080ff80808080808080808080ff018080
|
@ -5,17 +5,19 @@
|
||||
|
||||
(mod
|
||||
(
|
||||
MY_PUBKEY ; the public key of the owner used for signing transactions
|
||||
INNER_PUZZLE ; Standard P2 inner puzzle, used to record the ownership of the DID.
|
||||
RECOVERY_DID_LIST_HASH ; the list of DIDs that can send messages to you for recovery we store only the hash so that we don't have to reveal every time we make a message spend
|
||||
NUM_VERIFICATIONS_REQUIRED ; how many of the above list are required for a recovery
|
||||
Truths ; Truths are sent from the singleton layer
|
||||
mode ; this indicates which spend mode we want. Create message, recover, or self-destruct
|
||||
new_amount ; DIDs can receive payments so when we recreate ourselves sometimes we want to change our amount
|
||||
message ; this is a list of messages when creating a message spend, or a new puzhash when recovering or self destructing
|
||||
new_inner_puzhash ; this is used during the message creation spend to optionally give ourselves a new inner puzzle - this is useful for updating our recovery list
|
||||
SINGLETON_STRUCT ; my singleton_struct, formerly a Truth - ((SINGLETON_MOD_HASH, (LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
|
||||
METADATA ; Customized metadata, e.g KYC info
|
||||
mode ; this indicates which spend mode we want. 0. Recovery mode 1. Run INNER_PUZZLE with p2_solution
|
||||
my_amount_or_inner_solution ; In mode 0, we use this to recover our coin and assert it is our actual amount
|
||||
; In mode 1 this is the solution of the inner P2 puzzle, only required in the create message mode and transfer mode.
|
||||
new_inner_puzhash ; In recovery mode, this will be the new wallet DID puzzle hash
|
||||
parent_innerpuzhash_amounts_for_recovery_ids ; during a recovery we need extra information about our recovery list coins
|
||||
pubkey ; this is the new pubkey used for a recovery
|
||||
recovery_list_reveal ; this is the reveal of the stored list of DIDs approved for recovery
|
||||
my_id ; my coin ID
|
||||
)
|
||||
;message is the new puzzle in the recovery and standard spend cases
|
||||
|
||||
@ -24,7 +26,6 @@
|
||||
|
||||
(include condition_codes.clvm)
|
||||
(include curry-and-treehash.clinc)
|
||||
(include singleton_truths.clib)
|
||||
|
||||
; takes a lisp tree and returns the hash of it
|
||||
(defun sha256tree1 (TREE)
|
||||
@ -50,7 +51,7 @@
|
||||
)
|
||||
|
||||
|
||||
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
|
||||
; return the full puzzlehash for a singleton with the innerpuzzle curried in
|
||||
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
|
||||
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
|
||||
@ -59,104 +60,76 @@
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro create_new_coin (amount new_puz)
|
||||
(qq (c CREATE_COIN (c (unquote new_puz) (c (unquote amount) ()))))
|
||||
)
|
||||
|
||||
; this loops over our identities to check list, and checks if we have been given parent information for this identity
|
||||
; the reason for this is because we might only require 3/5 of the IDs give approval messages for a recovery
|
||||
; if we have the information for an identity then we create a consume message using that information
|
||||
|
||||
(defun check_messages_from_identities (SINGLETON_STRUCT num_verifications_required identities my_id output new_puz parent_innerpuzhash_amounts_for_recovery_ids pubkey num_verifications)
|
||||
(defun check_messages_from_identities (SINGLETON_STRUCT num_verifications_required identities my_id new_puz parent_innerpuzhash_amounts_for_recovery_ids pubkey num_verifications)
|
||||
(if identities
|
||||
(if (f parent_innerpuzhash_amounts_for_recovery_ids)
|
||||
; if we have parent information then we should create a consume coin condition
|
||||
(check_messages_from_identities
|
||||
SINGLETON_STRUCT
|
||||
num_verifications_required
|
||||
(r identities)
|
||||
my_id
|
||||
(c
|
||||
(create_consume_message
|
||||
; create coin_id from DID
|
||||
(create_coin_ID_for_recovery
|
||||
SINGLETON_STRUCT
|
||||
(f identities)
|
||||
(f (f parent_innerpuzhash_amounts_for_recovery_ids))
|
||||
(f (r (f parent_innerpuzhash_amounts_for_recovery_ids)))
|
||||
(f (r (r (f parent_innerpuzhash_amounts_for_recovery_ids)))))
|
||||
my_id
|
||||
new_puz
|
||||
pubkey)
|
||||
output)
|
||||
new_puz
|
||||
(r parent_innerpuzhash_amounts_for_recovery_ids)
|
||||
pubkey
|
||||
(+ num_verifications 1)
|
||||
(c
|
||||
(create_consume_message
|
||||
; create coin_id from DID
|
||||
(create_coin_ID_for_recovery
|
||||
SINGLETON_STRUCT
|
||||
(f identities)
|
||||
(f (f parent_innerpuzhash_amounts_for_recovery_ids))
|
||||
(f (r (f parent_innerpuzhash_amounts_for_recovery_ids)))
|
||||
(f (r (r (f parent_innerpuzhash_amounts_for_recovery_ids)))))
|
||||
my_id
|
||||
new_puz
|
||||
pubkey
|
||||
)
|
||||
(check_messages_from_identities
|
||||
SINGLETON_STRUCT
|
||||
num_verifications_required
|
||||
(r identities)
|
||||
my_id
|
||||
new_puz
|
||||
(r parent_innerpuzhash_amounts_for_recovery_ids)
|
||||
pubkey
|
||||
(+ num_verifications 1)
|
||||
)
|
||||
)
|
||||
; if no parent information found for this identity, move on to next in list
|
||||
(check_messages_from_identities
|
||||
SINGLETON_STRUCT
|
||||
(r identities)
|
||||
my_id
|
||||
output
|
||||
new_puz
|
||||
(r parent_innerpuzhash_amounts_for_recovery_ids)
|
||||
pubkey
|
||||
num_verifications
|
||||
)
|
||||
)
|
||||
;if we're out of identites to check for, return our output
|
||||
(if (> num_verifications num_verifications_required)
|
||||
(c (list AGG_SIG_UNSAFE pubkey new_puz) output)
|
||||
(if (= num_verifications num_verifications_required)
|
||||
(c (list AGG_SIG_UNSAFE pubkey new_puz) output)
|
||||
(x)
|
||||
)
|
||||
;if we're out of identites to check for, check we have enough
|
||||
(if (> num_verifications (- num_verifications_required 1))
|
||||
(list (list AGG_SIG_UNSAFE pubkey new_puz) )
|
||||
(x)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
; for a list of messages in the format (type . message) create a message
|
||||
; type 0 is 0 value coin
|
||||
; type 1 is coin announcement
|
||||
; type 2 is puzzle announcement
|
||||
(defun create_messages (messages)
|
||||
(if messages
|
||||
(c
|
||||
(if (f (f messages))
|
||||
(list (if (= (f (f messages)) 1) CREATE_COIN_ANNOUNCEMENT CREATE_PUZZLE_ANNOUNCEMENT) (r (f messages)))
|
||||
(list CREATE_COIN (r (f messages)) 0)
|
||||
)
|
||||
(create_messages (r messages))
|
||||
)
|
||||
()
|
||||
)
|
||||
)
|
||||
|
||||
;Spend modes:
|
||||
;0 = exit spend
|
||||
;1 = create messages and recreate singleton
|
||||
;2 = recovery
|
||||
;0 = recovery
|
||||
;1 = run the INNER_PUZZLE
|
||||
|
||||
;MAIN
|
||||
|
||||
(if mode
|
||||
(if (= mode 1)
|
||||
; mode one - create messages and recreate singleton
|
||||
(c (list CREATE_COIN new_inner_puzhash new_amount) (c (list AGG_SIG_ME MY_PUBKEY (sha256tree1 (list new_inner_puzhash new_amount message))) (create_messages message)))
|
||||
; mode two - recovery
|
||||
; check that recovery list is not empty
|
||||
(if recovery_list_reveal
|
||||
(if (= (sha256tree1 recovery_list_reveal) RECOVERY_DID_LIST_HASH)
|
||||
(check_messages_from_identities (singleton_struct_truth Truths) NUM_VERIFICATIONS_REQUIRED recovery_list_reveal (my_id_truth Truths) (list (create_new_coin new_amount message)) message parent_innerpuzhash_amounts_for_recovery_ids pubkey 0)
|
||||
(x)
|
||||
)
|
||||
(x)
|
||||
)
|
||||
)
|
||||
; mode zero - exit spend
|
||||
(list (list CREATE_COIN 0x00 -113) (list CREATE_COIN message new_amount) (list AGG_SIG_ME MY_PUBKEY (sha256tree1 (list new_amount message))))
|
||||
)
|
||||
; mode 1 - run INNER_PUZZLE
|
||||
(a INNER_PUZZLE my_amount_or_inner_solution)
|
||||
|
||||
; mode 0 - recovery
|
||||
(if (all (= (sha256tree1 recovery_list_reveal) RECOVERY_DID_LIST_HASH) (> NUM_VERIFICATIONS_REQUIRED 0))
|
||||
(c (list ASSERT_MY_AMOUNT my_amount_or_inner_solution)
|
||||
(c (list CREATE_COIN new_inner_puzhash my_amount_or_inner_solution (list new_inner_puzhash))
|
||||
(c (list ASSERT_MY_COIN_ID my_id)
|
||||
(check_messages_from_identities SINGLETON_STRUCT NUM_VERIFICATIONS_REQUIRED recovery_list_reveal my_id new_inner_puzhash parent_innerpuzhash_amounts_for_recovery_ids pubkey 0)
|
||||
)
|
||||
)
|
||||
)
|
||||
(x)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -1 +1 @@
|
||||
ff02ffff01ff02ffff03ff5fffff01ff02ffff03ffff09ff5fffff010180ffff01ff04ffff04ff24ffff04ff8202ffffff04ff81bfff80808080ffff04ffff04ff20ffff04ff05ffff04ffff02ff7effff04ff02ffff04ffff04ff8202ffffff04ff81bfffff04ff82017fff80808080ff80808080ff80808080ffff02ff36ffff04ff02ffff04ff82017fff808080808080ffff01ff02ffff03ff8217ffffff01ff02ffff03ffff09ffff02ff7effff04ff02ffff04ff8217ffff80808080ff0b80ffff01ff02ff3affff04ff02ffff04ff8201efffff04ff17ffff04ff8217ffffff04ff818fffff04ffff04ffff04ff24ffff04ff82017fffff04ff81bfff80808080ff8080ffff04ff82017fffff04ff8205ffffff04ff820bffffff01ff808080808080808080808080ffff01ff088080ff0180ffff01ff088080ff018080ff0180ffff01ff04ffff04ff24ffff01ff00ff818f8080ffff04ffff04ff24ffff04ff82017fffff04ff81bfff80808080ffff04ffff04ff20ffff04ff05ffff04ffff02ff7effff04ff02ffff04ffff04ff81bfffff04ff82017fff808080ff80808080ff80808080ff8080808080ff0180ffff04ffff01ffffffff3231ff3d02ffff333cff3eff0401ffffff0102ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff32ffff0bff7cff5c80ffff0bff32ffff0bff32ffff0bff7cff2280ff0980ffff0bff32ff0bffff0bff7cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ff82027fffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fffff04ffff04ffff04ff28ffff04ffff0bffff0bffff02ff26ffff04ff02ffff04ff05ffff04ff27ffff04ff82047fffff04ff820a7fffff04ff82167fff8080808080808080ffff02ff7effff04ff02ffff04ffff02ff2effff04ff02ffff04ff2fffff04ff81bfffff04ff8202ffff808080808080ff8080808080ff2f80ff808080ff5f80ffff04ff81bfffff04ff82037fffff04ff8202ffffff04ffff10ff8205ffffff010180ff808080808080808080808080ffff01ff02ff3affff04ff02ffff04ff05ffff04ff37ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff04ff8202ffffff04ff8205ffff808080808080808080808080ff0180ffff01ff02ffff03ffff15ff8205ffff0b80ffff01ff04ffff04ff30ffff04ff8202ffffff04ff81bfff80808080ff5f80ffff01ff02ffff03ffff09ff8205ffff0b80ffff01ff04ffff04ff30ffff04ff8202ffffff04ff81bfff80808080ff5f80ffff01ff088080ff018080ff018080ff0180ffffff0bff17ffff02ff5effff04ff02ffff04ff09ffff04ff2fffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff0bff1d8080ff80808080ff808080808080ff5f80ff02ffff03ff05ffff01ff04ffff02ffff03ff11ffff01ff04ffff02ffff03ffff09ff11ffff010180ffff0134ffff012c80ff0180ffff04ff19ff808080ffff01ff04ff24ffff04ff19ffff01ff8080808080ff0180ffff02ff36ffff04ff02ffff04ff0dff8080808080ff8080ff0180ffff04ffff0101ffff04ffff04ff34ffff04ff05ff808080ffff04ffff04ff30ffff04ff17ffff04ff0bff80808080ff80808080ffff0bff32ffff0bff7cff3880ffff0bff32ffff0bff32ffff0bff7cff2280ff0580ffff0bff32ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff7cff7c80ff8080808080ffff0bff7cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
||||
ff02ffff01ff02ffff03ff81bfffff01ff02ff05ff82017f80ffff01ff02ffff03ffff22ffff09ffff02ff7effff04ff02ffff04ff8217ffff80808080ff0b80ffff15ff17ff808080ffff01ff04ffff04ff28ffff04ff82017fff808080ffff04ffff04ff34ffff04ff8202ffffff04ff82017fffff04ffff04ff8202ffff8080ff8080808080ffff04ffff04ff38ffff04ff822fffff808080ffff02ff26ffff04ff02ffff04ff2fffff04ff17ffff04ff8217ffffff04ff822fffffff04ff8202ffffff04ff8205ffffff04ff820bffffff01ff8080808080808080808080808080ffff01ff088080ff018080ff0180ffff04ffff01ffffffff313dff4946ffff0233ff3c04ffffff0101ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff22ff3c80ffff0bff2affff0bff2affff0bff22ff3280ff0980ffff0bff2aff0bffff0bff22ff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff17ffff01ff02ffff03ff82013fffff01ff04ffff04ff30ffff04ffff0bffff0bffff02ff36ffff04ff02ffff04ff05ffff04ff27ffff04ff82023fffff04ff82053fffff04ff820b3fff8080808080808080ffff02ff7effff04ff02ffff04ffff02ff2effff04ff02ffff04ff2fffff04ff5fffff04ff82017fff808080808080ff8080808080ff2f80ff808080ffff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fffff04ff5fffff04ff8201bfffff04ff82017fffff04ffff10ff8202ffffff010180ff808080808080808080808080ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff37ffff04ff2fffff04ff5fffff04ff8201bfffff04ff82017fffff04ff8202ffff8080808080808080808080ff0180ffff01ff02ffff03ffff15ff8202ffffff11ff0bffff01018080ffff01ff04ffff04ff20ffff04ff82017fffff04ff5fff80808080ff8080ffff01ff088080ff018080ff0180ff0bff17ffff02ff5effff04ff02ffff04ff09ffff04ff2fffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff0bff1d8080ff80808080ff808080808080ff5f80ffff04ffff0101ffff04ffff04ff2cffff04ff05ff808080ffff04ffff04ff20ffff04ff17ffff04ff0bff80808080ff80808080ffff0bff2affff0bff22ff2480ffff0bff2affff0bff2affff0bff22ff3280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff22ff2280ff8080808080ffff0bff22ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
@ -1 +1 @@
|
||||
ef41902d9964f6050f87de98b5c4e34512b7d2abded3fe700f7850ff20323bf2
|
||||
33143d2bef64f14036742673afd158126b94284b4530a28c354fac202b0c910e
|
||||
|
25
chia/wallet/puzzles/json.clib
Normal file
25
chia/wallet/puzzles/json.clib
Normal file
@ -0,0 +1,25 @@
|
||||
(
|
||||
|
||||
(defun search_list_then_resume (key json_object result)
|
||||
(if result
|
||||
result
|
||||
(search_for_value_given_key key (r json_object))
|
||||
)
|
||||
)
|
||||
|
||||
(defun search_for_value_given_key (key json_object)
|
||||
(if json_object
|
||||
(if (l (f from_json_dict)) ; if we are hitting an unlabelled item (atom), just move on
|
||||
(if (= (f (f json_object)) key) ; if we have found our key
|
||||
(r (f json_object)) ; return the value
|
||||
(if (l (r (f json_object))) ; otherwise check if we have hit another dictionary
|
||||
(search_list_then_resume key json_object (search_for_value_given_key key (r (f json_object)))) ; check new dictionary
|
||||
(search_for_value_given_key key (r json_object))
|
||||
)
|
||||
)
|
||||
(search_for_value_given_key key (r json_object))
|
||||
)
|
||||
()
|
||||
)
|
||||
)
|
||||
)
|
85
chia/wallet/puzzles/nft_innerpuz.clvm
Normal file
85
chia/wallet/puzzles/nft_innerpuz.clvm
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
1
chia/wallet/puzzles/nft_innerpuz.clvm.hex
Normal file
1
chia/wallet/puzzles/nft_innerpuz.clvm.hex
Normal file
@ -0,0 +1 @@
|
||||
ff02ffff01ff04ffff04ff10ffff04ff12ff808080ffff02ffff03ff8202ffffff01ff02ffff03ffff09ffff02ff3effff04ff02ffff04ff820bffff80808080ff2f80ffff01ff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff02ff3effff04ff02ffff04ffff04ff8217ffffff04ff8202ffff808080ff8080808080ff808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff2fffff04ff5fffff04ff81bfffff04ff8202ffffff04ffff02ff820bffffff04ff81bfffff04ff5fffff04ff0bffff04ff17ffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff8217ffff80808080808080808080ffff01ff808080808080808080808080ffff01ff088080ff0180ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff81bfff80808080ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff0bff12ff2f80ffff04ffff0bff12ff1780ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff17ff8080ff8080808080ffff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff016180ff808080ffff04ffff04ff2cffff04ff17ff808080ff8080808080ff018080ffff04ffff01ffffff49ff3f02ff33ff3e04ffff01ff0102ffffff02ffff03ff05ffff01ff02ff26ffff04ff02ffff04ff0dffff04ffff0bff3affff0bff12ff3c80ffff0bff3affff0bff3affff0bff12ff2a80ff0980ffff0bff3aff0bffff0bff12ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff82017fffff01ff02ffff03ffff09ff82047fffff0181ea80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff820a7fff80808080ffff04ffff02ff3effff04ff02ffff04ff82167fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff01ff018080808080808080808080ffff01ff04ff82027fffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff04ff8202ffff80808080808080808080808080ff0180ffff01ff02ffff03ff8202ffff80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff02ff3effff04ff02ffff04ff2fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ff808080ff018080ff0180ffff0bff3affff0bff12ff3880ffff0bff3affff0bff3affff0bff12ff2a80ff0580ffff0bff3affff02ff26ffff04ff02ffff04ff07ffff04ffff0bff12ff1280ff8080808080ffff0bff12ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bff12ff058080ff0180ff018080
|
1
chia/wallet/puzzles/nft_innerpuz.clvm.hex.sha256tree
Normal file
1
chia/wallet/puzzles/nft_innerpuz.clvm.hex.sha256tree
Normal file
@ -0,0 +1 @@
|
||||
ae8ee2bf9e0bcc189d4d8efa3c8281235981abd75ade592ba71930aa6881f820
|
20
chia/wallet/puzzles/nft_metadata_updater.clvm
Normal file
20
chia/wallet/puzzles/nft_metadata_updater.clvm
Normal 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)
|
||||
)
|
||||
)
|
1
chia/wallet/puzzles/nft_metadata_updater.clvm.hex
Normal file
1
chia/wallet/puzzles/nft_metadata_updater.clvm.hex
Normal file
@ -0,0 +1 @@
|
||||
ff02ffff01ff02ffff03ff0bffff01ff04ffff02ff02ffff04ff02ffff04ff05ffff04ff0bff8080808080ffff01ff808080ffff01ff04ff05ffff01ff80808080ff0180ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080
|
@ -0,0 +1 @@
|
||||
3df9de54667a96f32eba322635f14d3474edadf23f396ba5a3e2e077a89a682a
|
21
chia/wallet/puzzles/nft_metadata_updater_default.clvm
Normal file
21
chia/wallet/puzzles/nft_metadata_updater_default.clvm
Normal 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)
|
||||
)
|
@ -0,0 +1 @@
|
||||
ff02ffff01ff04ffff04ffff02ffff03ff17ffff01ff02ff02ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff010580ff0180ffff04ff0bff808080ffff01ff808080ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080
|
@ -0,0 +1 @@
|
||||
81970d352e6a39a241eaf8ca510a0e669e40d778ba612621c60a50ef6cf29c7b
|
83
chia/wallet/puzzles/nft_ownership_transfer_program.clvm
Normal file
83
chia/wallet/puzzles/nft_ownership_transfer_program.clvm
Normal 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)))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
@ -0,0 +1 @@
|
||||
ff02ffff01ff04ffff04ffff04ff38ffff04ff81bfff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff5fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff17ff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e808080ff81bf80ff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff81bfff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e80808080ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff8202ffffff04ff15ff808080808080808080ff8080ffff04ffff01ffffff3fff0281eaffff3304ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff3480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff5dffff04ff2dffff04ffff0bffff0101ff5380ffff04ffff0bffff0101ff5d80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff24ffff04ff09ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
@ -0,0 +1 @@
|
||||
03557552e0bf8b200d8be8089f54786195ab8c07e3e07c39641f2f4f1fc2e48e
|
99
chia/wallet/puzzles/nft_state_layer.clvm
Normal file
99
chia/wallet/puzzles/nft_state_layer.clvm
Normal 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
|
||||
)
|
||||
)
|
||||
)
|
1
chia/wallet/puzzles/nft_state_layer.clvm.hex
Normal file
1
chia/wallet/puzzles/nft_state_layer.clvm.hex
Normal file
@ -0,0 +1 @@
|
||||
ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfffff01ff8080808080808080ffff04ffff01ffffff49ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ffff0181e880ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff05ffff04ff820167ff8080808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ffff04ff0bffff04ff05ff808080ffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff2bffff01ff02ffff03ffff09ff818bff3880ffff01ff02ffff03ffff18ff8202cbff3480ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff4bff80808080808080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff808080808080808080ff0180ffff01ff02ffff03ffff15ff818bff8080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff8080808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff8080808080808080ff018080ff0180ffff01ff02ffff03ff2fffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff81afffff04ffff0bff34ff5380ffff04ffff02ff2effff04ff02ffff04ff23ff80808080ffff04ffff0bff34ff0580ff8080808080808080ffff04ff17ff8201ef808080ff8080ffff01ff088080ff018080ff0180ff018080
|
1
chia/wallet/puzzles/nft_state_layer.clvm.hex.sha256tree
Normal file
1
chia/wallet/puzzles/nft_state_layer.clvm.hex.sha256tree
Normal file
@ -0,0 +1 @@
|
||||
dd8135d546e291df295b376aa89fc409c8c50d7f655d1ff4e845637901bc2f8f
|
139
chia/wallet/puzzles/nft_transfer_program.clvm
Normal file
139
chia/wallet/puzzles/nft_transfer_program.clvm
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
1
chia/wallet/puzzles/nft_transfer_program.clvm.hex
Normal file
1
chia/wallet/puzzles/nft_transfer_program.clvm.hex
Normal file
@ -0,0 +1 @@
|
||||
ff02ffff01ff02ff2affff04ff02ffff04ff05ffff04ff0bffff04ff820affffff04ffff02ff3affff04ff02ffff04ff17ffff04ff2fffff04ff0bffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffff80808080808080808080ff80808080808080ffff04ffff01ffffffff3f02ff333effff0401ff01ff82271002ffffffff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff22ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff7cffff0bff34ff2480ffff0bff7cffff0bff7cffff0bff34ff2c80ff0980ffff0bff7cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff04ffff04ffff0181eaffff04ffff02ff22ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff04ff0bff80808080ff2f80ffff012f80ff0180ff02ffff03ff82017fffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ffff02ff7effff04ff02ffff04ff82017fff80808080ff8080808080808080808080ffff01ff04ffff04ff38ffff01ff808080ff808080ff0180ffffff04ffff04ff38ffff04ff8202ffff808080ffff04ffff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff81bfffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff5fff1d8080ff80808080ff808080808080ff8202ffff1580ff808080ffff02ff36ffff04ff02ffff04ff17ffff04ff82017fffff04ff15ff8080808080808080ff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff5dffff04ff2dffff04ffff0bff34ff5380ffff04ffff0bff34ff5d80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff28ffff04ff09ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ffff0bff7cffff0bff34ff3080ffff0bff7cffff0bff7cffff0bff34ff2c80ff0580ffff0bff7cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080
|
@ -0,0 +1 @@
|
||||
519b806c31cce5280fa37695149521a301f13f5696d1fd1eeee1856c2d14965c
|
@ -10,7 +10,7 @@
|
||||
; synthetic_key_offset: a private key cryptographically generated using the hidden
|
||||
; puzzle and as inputs `original_public_key`
|
||||
;
|
||||
; synthetic_public_key: the public key that is the sum of `original_public_key` and the
|
||||
; SYNTHETIC_PUBLIC_KEY: the public key that is the sum of `original_public_key` and the
|
||||
; public key corresponding to `synthetic_key_offset`
|
||||
;
|
||||
; original_public_key: a public key, where knowledge of the corresponding private key
|
||||
@ -23,17 +23,17 @@
|
||||
|
||||
|
||||
(mod
|
||||
; A puzzle should commit to `synthetic_public_key`
|
||||
; A puzzle should commit to `SYNTHETIC_PUBLIC_KEY`
|
||||
;
|
||||
; The solution should pass in 0 for `original_public_key` if it wants to use
|
||||
; an arbitrary `delegated_puzzle` (and `solution`) signed by the
|
||||
; `synthetic_public_key` (whose corresponding private key can be calculated
|
||||
; `SYNTHETIC_PUBLIC_KEY` (whose corresponding private key can be calculated
|
||||
; if you know the private key for `original_public_key`)
|
||||
;
|
||||
; Or you can solve the hidden puzzle by revealing the `original_public_key`,
|
||||
; the hidden puzzle in `delegated_puzzle`, and a solution to the hidden puzzle.
|
||||
|
||||
(synthetic_public_key original_public_key delegated_puzzle solution)
|
||||
(SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle solution)
|
||||
|
||||
; "assert" is a macro that wraps repeated instances of "if"
|
||||
; usage: (assert A0 A1 ... An R)
|
||||
@ -61,9 +61,9 @@
|
||||
|
||||
; "is_hidden_puzzle_correct" returns true iff the hidden puzzle is correctly encoded
|
||||
|
||||
(defun-inline is_hidden_puzzle_correct (synthetic_public_key original_public_key delegated_puzzle)
|
||||
(defun-inline is_hidden_puzzle_correct (SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle)
|
||||
(=
|
||||
synthetic_public_key
|
||||
SYNTHETIC_PUBLIC_KEY
|
||||
(point_add
|
||||
original_public_key
|
||||
(pubkey_for_exp (sha256 original_public_key (sha256tree1 delegated_puzzle)))
|
||||
@ -73,19 +73,19 @@
|
||||
|
||||
; "possibly_prepend_aggsig" is the main entry point
|
||||
|
||||
(defun-inline possibly_prepend_aggsig (synthetic_public_key original_public_key delegated_puzzle conditions)
|
||||
(defun-inline possibly_prepend_aggsig (SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle conditions)
|
||||
(if original_public_key
|
||||
(assert
|
||||
(is_hidden_puzzle_correct synthetic_public_key original_public_key delegated_puzzle)
|
||||
(is_hidden_puzzle_correct SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle)
|
||||
conditions
|
||||
)
|
||||
(c (list AGG_SIG_ME synthetic_public_key (sha256tree1 delegated_puzzle)) conditions)
|
||||
(c (list AGG_SIG_ME SYNTHETIC_PUBLIC_KEY (sha256tree1 delegated_puzzle)) conditions)
|
||||
)
|
||||
)
|
||||
|
||||
; main entry point
|
||||
|
||||
(possibly_prepend_aggsig
|
||||
synthetic_public_key original_public_key delegated_puzzle
|
||||
SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle
|
||||
(a delegated_puzzle solution))
|
||||
)
|
||||
|
@ -1 +1 @@
|
||||
ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
||||
ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
11
chia/wallet/puzzles/sha256tree.clib
Normal file
11
chia/wallet/puzzles/sha256tree.clib
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
124
chia/wallet/puzzles/singleton_top_layer_v1_1.clvm
Normal file
124
chia/wallet/puzzles/singleton_top_layer_v1_1.clvm
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
1
chia/wallet/puzzles/singleton_top_layer_v1_1.clvm.hex
Normal file
1
chia/wallet/puzzles/singleton_top_layer_v1_1.clvm.hex
Normal file
@ -0,0 +1 @@
|
||||
ff02ffff01ff02ffff03ffff18ff2fff3c80ffff01ff04ffff04ff10ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff0bff27ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff02ffff03ff77ffff0181b7ffff015780ff018080ffff04ff77ff808080808080ffff02ff26ffff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffff49ff4702ff33ff0401ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff02ffff03ffff02ffff03ffff09ff23ff1480ffff01ff02ffff03ffff18ff81b3ff3c80ffff01ff0101ff8080ff0180ff8080ff0180ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff3cff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff3cff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ff0bff2affff0bff3cff3880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff3cff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff28ffff04ff0bff808080ffff01ff088080ff0180ff018080
|
@ -0,0 +1 @@
|
||||
f1e8350cec62f8204aaf867cc3c12cae369f619258206616108c6cfd7be760b3
|
17
chia/wallet/util/json_clvm_utils.py
Normal file
17
chia/wallet/util/json_clvm_utils.py
Normal 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)
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
1
setup.py
1
setup.py
@ -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",
|
||||
|
@ -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",
|
||||
|
3
tests/wallet/did_wallet/config.py
Normal file
3
tests/wallet/did_wallet/config.py
Normal file
@ -0,0 +1,3 @@
|
||||
# flake8: noqa: E501
|
||||
job_timeout = 50
|
||||
checkout_blocks_and_plots = True
|
@ -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
|
||||
|
@ -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()
|
0
tests/wallet/nft_wallet/__init__.py
Normal file
0
tests/wallet/nft_wallet/__init__.py
Normal file
1
tests/wallet/nft_wallet/config.py
Normal file
1
tests/wallet/nft_wallet/config.py
Normal file
@ -0,0 +1 @@
|
||||
checkout_blocks_and_plots = True
|
93
tests/wallet/nft_wallet/test_nft_clvm.py
Normal file
93
tests/wallet/nft_wallet/test_nft_clvm.py
Normal file
@ -0,0 +1,93 @@
|
||||
from chia.types.blockchain_format.program import INFINITE_COST, Program
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm
|
||||
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_for_pk, solution_for_conditions
|
||||
from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition
|
||||
from tests.core.make_block_generator import int_to_public_key
|
||||
|
||||
SINGLETON_MOD = load_clvm("singleton_top_layer.clvm")
|
||||
LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
|
||||
DID_MOD = load_clvm("did_innerpuz.clvm")
|
||||
NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm")
|
||||
STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm")
|
||||
LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash()
|
||||
NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
|
||||
SINGLETON_MOD_HASH = SINGLETON_MOD.get_tree_hash()
|
||||
|
||||
LAUNCHER_ID = Program.to("launcher-id").get_tree_hash()
|
||||
NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.clvm")
|
||||
|
||||
|
||||
def test_new_nft_ownership_layer() -> None:
|
||||
pubkey = int_to_public_key(1)
|
||||
innerpuz = puzzle_for_pk(pubkey)
|
||||
my_amount = 1
|
||||
destination: Program = puzzle_for_pk(int_to_public_key(2))
|
||||
condition_list = [make_create_coin_condition(destination.get_tree_hash(), my_amount, [])]
|
||||
metadata = [
|
||||
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
|
||||
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
|
||||
]
|
||||
solution = Program.to(
|
||||
[
|
||||
NFT_STATE_LAYER_MOD_HASH,
|
||||
metadata,
|
||||
NFT_METADATA_UPDATER.get_tree_hash(),
|
||||
innerpuz,
|
||||
# below here is the solution
|
||||
solution_for_conditions(condition_list),
|
||||
my_amount,
|
||||
0,
|
||||
]
|
||||
)
|
||||
|
||||
cost, res = NFT_STATE_LAYER_MOD.run_with_cost(INFINITE_COST, solution)
|
||||
assert res.first().first().as_int() == 73
|
||||
assert res.first().rest().first().as_int() == 1
|
||||
assert res.rest().rest().first().first().as_int() == 51
|
||||
assert (
|
||||
res.rest().rest().first().rest().first().as_atom()
|
||||
== NFT_STATE_LAYER_MOD.curry(
|
||||
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination
|
||||
).get_tree_hash()
|
||||
)
|
||||
|
||||
|
||||
def test_update_metadata() -> None:
|
||||
pubkey = int_to_public_key(1)
|
||||
innerpuz = puzzle_for_pk(pubkey)
|
||||
my_amount = 1
|
||||
destination: Program = puzzle_for_pk(int_to_public_key(2))
|
||||
condition_list = [make_create_coin_condition(destination.get_tree_hash(), my_amount, [])]
|
||||
condition_list.append([-24, NFT_METADATA_UPDATER, "https://www.chia.net/img/branding/chia-logo-2.svg"])
|
||||
metadata = [
|
||||
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
|
||||
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
|
||||
]
|
||||
solution = Program.to(
|
||||
[
|
||||
NFT_STATE_LAYER_MOD_HASH,
|
||||
metadata,
|
||||
NFT_METADATA_UPDATER.get_tree_hash(),
|
||||
innerpuz,
|
||||
# below here is the solution
|
||||
solution_for_conditions(condition_list),
|
||||
my_amount,
|
||||
0,
|
||||
]
|
||||
)
|
||||
|
||||
metadata = [
|
||||
("u", ["https://www.chia.net/img/branding/chia-logo-2.svg", "https://www.chia.net/img/branding/chia-logo.svg"]),
|
||||
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
|
||||
]
|
||||
|
||||
cost, res = NFT_STATE_LAYER_MOD.run_with_cost(INFINITE_COST, solution)
|
||||
assert res.first().first().as_int() == 73
|
||||
assert res.first().rest().first().as_int() == 1
|
||||
assert res.rest().rest().first().first().as_int() == 51
|
||||
assert (
|
||||
res.rest().rest().first().rest().first().as_atom()
|
||||
== NFT_STATE_LAYER_MOD.curry(
|
||||
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination
|
||||
).get_tree_hash()
|
||||
)
|
468
tests/wallet/nft_wallet/test_nft_wallet.py
Normal file
468
tests/wallet/nft_wallet/test_nft_wallet.py
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user