Skip to content

Latest commit

 

History

History
581 lines (484 loc) · 22.1 KB

README.md

File metadata and controls

581 lines (484 loc) · 22.1 KB

RGB Sandbox

Introduction

This is an RGB sandbox and demo based on RGB version 0.10. It is based on the original rgb-node demo by St333p (version 0.1), grunch's guide and previous rgb-node sandbox versions.

The underlying Bitcoin network is regtest.

RGB is operated via the rgb-contracts crate. BDK is used for walleting.

This sandbox can help explore RGB features in a self-contained environment or can be used as a demo of the main RGB functionalities for fungible assets.

Two versions of the demo are available:

  • an automated one
  • a manual one

The automated version is meant to provide a quick and easy way to see an RGB token be created and transferred. The manual version is meant to provide a hands-on experience with an RGB token and gives step-by-step instructions on how to operate all the required components.

Commands are to be executed in a bash shell. Example output is provided to allow following the links between the steps. Actual output when executing the procedure will be different each time.

Setup

Clone the repository, including (shallow) submodules:

git clone https://github.com/RGB-Tools/rgb-sandbox --recurse-submodules --shallow-submodules

The default setup assumes the user and group IDs are 1000. If that's not the case, the MYUID and MYGID environment variables in the docker-compose.yml file need to be updated accordingly.

The automated demo does not require any other setup steps.

The manual version requires handling of data directories and services, see the dedicated section for instructions.

Both versions will leave bdk-cli and rgb-contracts installed, in the respective directories under the project root. These directories can be safely removed to start from scratch, doing so will just require the rust crates to be re-installed on the next run.

Requirements

Sandbox exploration

The services started with docker compose simulate a small network with a bitcoin node and an explorer. These can be used to support testing and exploring the basic functionality of an RGB ecosystem.

Check out the manual demo below to get started with example commands. Refer to each command's help documentation for additional information.

Automated demo

To check out the automated demo, run:

bash demo.sh

The automated script will install the required rust crates, create empty service data directories, start the required services, prepare the wallets, issue assets, execute a series of asset transfers, then stop the services and remove the data directories.

For more verbose output during the automated demo, add the -v option (bash demo.sh -v), which shows the commands being run and additional information (including output from additional inspection commands).

Manual demo recording

Following the manual demo and executing all the required steps is a rather long and error-prone process.

To ease the task of following the steps, a recording of the manual demo execution is available: demo

Manual demo

Note: this has not yet been updated to the 0.10 version.

The manual demo shows how to issue an asset and transfer some to a recipient.

At the beginning of the demo, some shell command aliases and common variables need to be set, then a series of steps are briefly described and illustrated with example shell commands.

During each step, commands either use literal values, ones that the user needs to fill in, or variables. Some variables (uppercase) are the ones set at the beginning of the demo, others (lowercase) need to be set based on the output of the commands as they are run.

Values that need to be filled in with command output follow the command invocation that produces the required output and the example value is ellipsized (...), meaning the instruction should not be copied verbatim and the value should instead be replaced with the actual output received while following the steps.

Data and service management

Create data directories and start the required services in Docker containers:

# create data directories
mkdir data{0,1,core,index}

# start services (first time docker images need to be downloaded...)
docker compose up -d

To get a list of the running services you can run:

docker compose ps

To get their respective logs you can run, for instance:

docker compose logs bitcoind

Once finished and in order to clean up containers and data to start the demo from scratch, run:

# stop services and remove containers
docker compose down

# remove data directories
rm -fr data{0,1,core,index}

Premise

The rgb-contracts CLI tool does not handle wallet-related functionality, it performs RGB-specific tasks over data that is provided by an external wallet, such as BDK. In particular, in order to demonstrate a basic workflow with issuance and transfer, from the bitcoin wallets we will need:

  • an outpoint_issue to which the issuer will allocate the new asset
  • an outpoint_receive where the recipient will receive the asset transfer
  • an addr_change where the sender will receive the bitcoin and asset change
  • a partially signed bitcoin transaction (PSBT) to anchor the transfer

bdk-cli installation

Wallets will be handled with BDK. We install its CLI to the bdk-cli directory inside the project directory:

cargo install bdk-cli --version "0.27.1" --root "./bdk-cli" --features electrum --locked

rgb-contracts installation

RGB functionality will be handled with rgb-contracts. We install its CLI to the rgb-contracts directory inside the project directory:

cargo install rgb-contracts --version "0.10.0-rc.5" --root "./rgb-contracts" --all-features --locked

Demo

Initial setup

We setup aliases to ease CLI calls:

alias bcli="docker compose exec -u blits bitcoind bitcoin-cli -regtest"
alias bdk="bdk-cli/bin/bdk-cli"
alias rgb0="rgb-contracts/bin/rgb -n regtest -d data0"
alias rgb1="rgb-contracts/bin/rgb -n regtest -d data1"

We set some environment variables:

CLOSING_METHOD="opret1st"
DERIVE_PATH="m/86'/1'/0'/9"
DESC_TYPE="wpkh"
ELECTRUM="localhost:50001"
ELECTRUM_DOCKER="electrs:50001"
CONSIGNMENT="consignment.rgb"
PSBT="tx.psbt"
IFACE="RGB20"

We prepare the Bitcoin wallets using Bitcoin Core and BDK:

# Bitcoin Core wallet
bcli createwallet miner
bcli -generate 103

# if there are any bdk wallets from previous runs, they need to be removed
rm -fr ~/.bdk-bitcoin/{issuer,receiver}

# issuer/sender BDK wallet
bdk key generate
# example output:
# {
#   "fingerprint": "a83fc09c",
#   "mnemonic": "frozen nest frown retire wolf clinic tent know culture mad season whip impulse adjust hand change stomach meat wreck brick foam broken start reform",
#   "xprv": "tprv8ZgxMBicQKsPey8NKMEpFemmWPMtYb4znAKtWVKr48Q1uDvemxT3RRW5m6NpToMoiVYSwVS16xKkeMueVhxnUsE7X7TpgzzxLSg7jBS1ma2"
# }

xprv_0="tprv8Zgx..."

bdk key derive -p "$DERIVE_PATH" -x "$xprv_0"
# example output:
# {
#   "xprv": "[a83fc09c/86'/1'/0'/9]tprv8iqTQS7ksLhSaJNCfach4uTD6NJtoypuYnSEM5no1Km6YkCt8ciaYxtNL8pR68KU8a7GDSMhRTsrjaH7QR5bsx2e4287tjBa7SdFGyStGPR/*",
#   "xpub": "[a83fc09c/86'/1'/0'/9]tpubDFXVYrA11iP7TmPzZEHHUK7KfPppyK1p8631dbq6RbZVPETem1YAjTWEWK6xkwQpJpcvcX6vGZ8xoK6yLE7CcRnm4514mhHGfJ1UNLHVxXG/*"
# }

xprv_der_0="[a83fc09c/86'/1'/0'/9]tprv8iqT..."
xpub_der_0="[a83fc09c/86'/1'/0'/9]tpubDFXV..."

# receiver BDK wallet
bdk key generate
# example output:
# {
#   "fingerprint": "2976d70f",
#   "mnemonic": "kick detail chronic crime unusual nut legal viable limb elegant always tent envelope betray comfort human famous boat garment shallow hunt brass mind bomb",
#   "xprv": "tprv8ZgxMBicQKsPdorFhbmRFNu9tWMy2xxLLRYvxE5bpSvhymTpUmHdgaZiXN3ndATpSRTyyaxpnve3xYwhdoUhW1DwCW85MW9KwuJzV2xX6gV"
# }

xprv_1="tprv8Zgx..."

bdk key derive -p "$DERIVE_PATH" -x "$xprv_1"
# example output:
# {
#   "xprv": "[2976d70f/86'/1'/0'/9]tprv8j496FRBryAmENLx7h66pKcgtet1o7fJN5yv4jVvZ8cpcDVTWajqqmyNFmrv9buLR7UkhqFKuvshcZQxdzfCgN1Qg1m5UcHLA5PRumTBvv7/*",
#   "xpub": "[2976d70f/86'/1'/0'/9]tpubDFkBEfTS1LrS7qNk1LkhDjGoTgPwxSrCwPahMFYDyQRDShkE8yZS2GbERvepZ9mNAK5R4ejNmDoFFv1EHZ8QgJwqkXFmn6C1spUa6VUwr1x/*"
# }

xprv_der_1="[2976d70f/86'/1'/0'/9]tprv8j49..."
xpub_der_1="[2976d70f/86'/1'/0'/9]tpubDFkB..."

# generate addresses
bdk -n regtest wallet -w issuer -d "$DESC_TYPE($xpub_der_0)" get_new_address
# example output:
# {
#   "address": "bcrt1q67z8nmswgvs38n64yl80plsejcs6vt867c2y22"
# }

addr_issue="bcrt1q67..."

bdk -n regtest wallet -w issuer -d "$DESC_TYPE($xpub_der_0)" get_new_address
# example output:
# {
#   "address": "bcrt1qr36fkwcvaqkg4v5e2hdh9x4vrxhqysf6wk4hcn"
# }

addr_change="bcrt1qr3..."

bdk -n regtest wallet -w receiver -d "$DESC_TYPE($xpub_der_1)" get_new_address
# example output:
# {
#   "address": "bcrt1q87w6s0anaugzksgmq9adwcgw9wyt6ekj7u8qc6"
# }

addr_receive="bcrt1q87..."

# fund wallets
bcli -rpcwallet=miner sendtoaddress "$addr_issue" 1
bcli -rpcwallet=miner sendtoaddress "$addr_receive" 1
bcli -rpcwallet=miner -generate 1

# sync wallets
bdk -n regtest wallet -w issuer -d "$DESC_TYPE($xpub_der_0)" -s "$ELECTRUM" sync
bdk -n regtest wallet -w receiver -d "$DESC_TYPE($xpub_der_1)" -s "$ELECTRUM" sync

# list wallet unspents and gather the outpoints
bdk -n regtest wallet -w issuer -d "$DESC_TYPE($xpub_der_0)" list_unspent
# example output:
# [
#   {
#     "is_spent": false,
#     "keychain": "External",
#     "outpoint": "6f6343401fc57c3f6a30043c61023e62311ee2b5d321823843af9cbcbfb2ac7e:1",
#     "txout": {
#       "script_pubkey": "0014d78479ee0e432113cf5527cef0fe199621a62cfa",
#       "value": 100000000
#     }
#   }
# ]

outpoint_issue="6f6...c7e:1"

bdk -n regtest wallet -w receiver -d "$DESC_TYPE($xpub_der_1)" list_unspent
# example output:
# [
#   {
#     "is_spent": false,
#     "keychain": "External",
#     "outpoint": "bbc274a1f145552a6f22cab912c9b1903fb30333128b3b0f22212f2aa87772e2:0",
#     "txout": {
#       "script_pubkey": "00143f9da83fb3ef102b411b017ad7610e2b88bd66d2",
#       "value": 100000000
#     }
#   }
# ]

outpoint_receive="bbc...2e2:0"

We setup the RGB clients, importing schema and interface implementation:

# 1st client
rgb0 import rgb-schemata/schemata/NonInflatableAssets.rgb
# example output:
# Stock file not found, creating default stock
# Wallet file not found, creating new wallet list
# Schema urn:lnp-bp:sc:BEiLYE-am9WhTW1-oK8cpvw4-FEMtzMrf-mKocuGZn-qWK6YF#ginger-parking-nirvana imported to the stash

rgb0 import rgb-schemata/schemata/NonInflatableAssets-RGB20.rgb
# example output:
# Implementation urn:lnp-bp:im:9EUGHC-wpuiyrQE-NdPBVyiv-VX4sVRBs-9yKfteug-HtqnGb#titanic-easy-citizen of interface urn:lnp-bp:if:48hc4i-m9JRcYQA-uUSzwFCK-VNEa9eZf-nhepU8QJ-pqosXS#laptop-domingo-cool for schema urn:lnp-bp:sc:BEiLYE-am9WhTW1-oK8cpvw4-FEMtzMrf-mKocuGZn-qWK6YF#ginger-parking-nirvana imported to the stash

# 2nd client (same output as 1st client)
rgb1 import rgb-schemata/schemata/NonInflatableAssets.rgb
rgb1 import rgb-schemata/schemata/NonInflatableAssets-RGB20.rgb

We retrieve the schema ID and set it as environment variable:

rgb0 schemata
# example output:
# urn:lnp-bp:sc:BEiLYE-am9WhTW1-oK8cpvw4-FEMtzMrf-mKocuGZn-qWK6YF#ginger-parking-nirvana RGB20

schema="urn:lnp-bp:sc:BEiLYE-am...ing-nirvana"

Asset issuance

To issue an asset, we first need to prepare a contract definition file, then use it to actually carry out the issuance.

To prepare the contract file, we copy the provided template and modify the copy to set the required data:

  • issued supply
  • created timestamp
  • closing method
  • issuance txid and vout

We do this with a single command (which reads the template file, modifies the given properties and writes the result to the contract definition file):

sed \
  -e "s/issued_supply/1000/" \
  -e "s/created_timestamp/$(date +%s)/" \
  -e "s/closing_method/$CLOSING_METHOD/" \
  -e "s/txid:vout/$outpoint_issue/" \
  contracts/usdt.yaml.template > contracts/usdt.yaml

To actually issue the asset, run:

rgb0 issue "$schema" "$IFACE" contracts/usdt.yaml
# example output:
# A new contract rgb:2Q7p6zS-JUCTP8pMJ-7fk8QBjYp-ngvHnJiuw-9jh8PPuvy-scKUUkd is issued and added to the stash.
# Use `export` command to export the contract.

contract_id="rgb:2Q7...Ukd"

This will create a new genesis that includes the asset metadata and the allocation of the initial amount to outpoint_issue.

You can list known contracts:

rgb0 contracts
# example output:
# rgb:2Q7p6zS-JUCTP8pMJ-7fk8QBjYp-ngvHnJiuw-9jh8PPuvy-scKUUkd

You can show the current known state for the contract:

rgb0 state "$contract_id" "$IFACE"
# example output:
# Global:
#   spec := (naming=(ticker=("USDT"), name=("USD Tether"), details=~), precision=0)
#   data := (terms=("demo RGB20 asset"), media=~)
#   issuedSupply := (1000)
#   created := (1691496693)
#
# Owned:
#   assetOwner:
#     amount=1000, utxo=6f6343401fc57c3f6a30043c61023e62311ee2b5d321823843af9cbcbfb2ac7e:1, witness=~ # owner unknown

Transfer

Receiver: generate invoice

In order to receive assets, the receiver needs to provide an invoice to the sender. The receiver generates an invoice providing the amount to be received (here 100) and the outpoint where the assets should be allocated:

rgb1 invoice "$contract_id" "$IFACE" 100 "$CLOSING_METHOD:$outpoint_receive"
# example output:
# rgb:2Q7p6zS-JUCTP8pMJ-7fk8QBjYp-ngvHnJiuw-9jh8PPuvy-scKUUkd/RGB20/100+utxob:ZCTkvDN-mrwDXLSkx-1PKfHuaGH-R7cLf79Rg-YfSUGYn6i-YAS4Ts

invoice="rgb:2Q7...Ukd/RGB20/100+utxob:ZCT...4Ts"

Note: this will blind the given outpoint and the invoice will contain a blinded UTXO in place of the original outpoint (see the utxob: part of the invoice).

Sender: initiate asset transfer

To send assets, the sender needs to create a consignment and commit to it into a bitcoin transaction. We need to create a PSBT and then modify it to include the commitment.

We create the PSBT, using outpoint_issue as input and addr_change for the change (RGB and BTC):

bdk -n regtest wallet -w issuer -d "$DESC_TYPE($xpub_der_0)" create_tx \
  -f 5 --send_all --utxos "$outpoint_issue" --to "$addr_change:0" \
  --add_string opret
# example output:
# {
#   "details": {
#     "confirmation_time": null,
#     "fee": 630,
#     "received": 99999370,
#     "sent": 100000000,
#     "transaction": null,
#     "txid": "26a1250ec087138e663cb7be52e13b18758e0257d9aba79d3f6eb0b0516763dd"
#   },
#   "psbt": "cHNidP8BAGIBAAAAAX6ssr+8nK9DOIIh07XiHjFiPgJhPAQwaj98xR9AQ2NvAQAAAAD+////Aore9QUAAAAAFgAUHHSbOwzoLIqymVXbcpqsGa4CQToAAAAAAAAAAAdqBW9wcmV0aAAAAAABAN4CAAAAAAEB9PcBuZVCyKTgMO50SVrsjfqdlVTABBB3cMtZaGXMN2MAAAAAAP3///8C/AUQJAEAAAAWABRhzatgiLp38YKt+2na/iB6/Y2MnADh9QUAAAAAFgAU14R57g5DIRPPVSfO8P4ZliGmLPoCRzBEAiAdCmZ/hSk/rZr+G+SGo/Lx8O8ZpAjIihTYcF983h796wIgHnKVCPwfmTIn+EPl8pDXbzHRTVnAn5jBRDCaMYS1SeMBIQPkcWZ9eqvZKlG1QK4t4vplBvahq6fHGp/lezPo9+pznWcAAAABAR8A4fUFAAAAABYAFNeEee4OQyETz1UnzvD+GZYhpiz6IgYClNhrjr91okI1jPK4r97icFId4fmZHKPciyYh7UBuX+MYqD/AnFYAAIABAACAAAAAgAkAAAAAAAAAACICAquWbk66NttJRZuWBIjM4NIbB1bT0/pH5VKyViGyey5TGKg/wJxWAACAAQAAgAAAAIAJAAAAAQAAAAAA"
# }

echo "cHN...AAA" | base64 -d > "data0/$PSBT"

We then modify the PSBT to set the commitment host:

rgb0 set-host --method "$CLOSING_METHOD" "data0/$PSBT"
# PSBT file 'data0/tx.psbt' is updated with opret1st host now set.

We create the transfer, providing the PSBT and the invoice. This generates the consignment:

rgb0 transfer --method "$CLOSING_METHOD" "data0/$PSBT" "$invoice" "data0/$CONSIGNMENT"
# example output:
# Transfer is created and saved into 'data0/consignment.rgb'.
# PSBT file 'data0/tx.psbt' is updated with all required commitments and ready to be signed.
# Stash data are updated.

The consignment can be inspected, but since the output is very long it's best to send the output to a file:

rgb0 inspect "data0/$CONSIGNMENT" > consignment.inspect

To view the result, open the consignment.inspect file with a text editor.

Consignment exchange

For the purpose of this demo, copying the file over to the receiving node's data directory is sufficient:

cp data{0,1}/"$CONSIGNMENT"

In real-world scenarios, consignments are exchanged either via RGB HTTP JSON-RPC (e.g. using an RGB proxy) or other consignment exchange services.

Receiver: validate transfer

Before a transfer can be safely accepted, it needs to be validated:

rgb1 validate "data1/$CONSIGNMENT"
# example output:
# Consignment has non-mined terminal(s)
# Non-mined terminals:
# - f17d544c0ac161f758d379c4366e6ede8f394da9633671908738b415ae5c8fb4
# Validation warnings:
# - terminal witness transaction f17d544c0ac161f758d379c4366e6ede8f394da9633671908738b415ae5c8fb4 is not yet mined.

At this point it's normal that validation reports a warning about the witness transaction not been mined, as the sender has not broadcast it yet. The sender is waiting for approval from the receiver.

Once validation has passed, the receiver can approve the transfer. For this demo let's just assume it happened, in a real-world scenario an RGB proxy would be typically used for this as well.

Sender: sign and broadcast transaction

With the receiver's approval of the transfer, the transaction can be signed and broadcast:

bdk -n regtest wallet -w issuer -d "$DESC_TYPE($xprv_der_0)" \
  sign --psbt $(cat data0/$PSBT | base64 | tr -d '\r\n')
# example output:
# {
#   "is_finalized": true,
#   "psbt": "cHNidP8BAH0BAAAAAX6ssr+8nK9DOIIh07XiHjFiPgJhPAQwaj98xR9AQ2NvAQAAAAD+////Aore9QUAAAAAFgAUHHSbOwzoLIqymVXbcpqsGa4CQToAAAAAAAAAACJqILJNu5OPAn29xMK30LPyEYF8BQzo4m4kMqdwQOf+vFMvaAAAACb8A1JHQgGzGylFw6I59wDVj85LlfJSDWRSj1ETsk8raL8NsrHYvNYAALgv5W39fUCA+5Wg2rcfqnBDyRzss7SzfYf8Km8dWDCRECcAAAABuC/lbf19QID7laDatx+qcEPJHOyztLN9h/wqbx1YMJGgDwAAAAGgDwECAAMAAAAAAAAPvcNrn//UdwiEAwAAAAAAAB1R+iZ9g65EZQH+yo6U6Rd5BR49w6kNrogl9GhgX2lPAkkbzeRxlHm2NcOArOXeqIFc56HypcJNjYqcpHODx2ySCGQAAAAAAAAAU/umctyJSLYssZtL73I+o2LndtWGDjf0niWCCTgYfVgAAAEA3gIAAAAAAQH09wG5lULIpOAw7nRJWuyN+p2VVMAEEHdwy1loZcw3YwAAAAAA/f///wL8BRAkAQAAABYAFGHNq2CIunfxgq37adr+IHr9jYycAOH1BQAAAAAWABTXhHnuDkMhE89VJ87w/hmWIaYs+gJHMEQCIB0KZn+FKT+tmv4b5Iaj8vHw7xmkCMiKFNhwX3zeHv3rAiAecpUI/B+ZMif4Q+XykNdvMdFNWcCfmMFEMJoxhLVJ4wEhA+RxZn16q9kqUbVAri3i+mUG9qGrp8can+V7M+j36nOdZwAAAAEBHwDh9QUAAAAAFgAU14R57g5DIRPPVSfO8P4ZliGmLPoiBgKU2GuOv3WiQjWM8riv3uJwUh3h+Zkco9yLJiHtQG5f4xioP8CcVgAAgAEAAIAAAACACQAAAAAAAAABBwABCGsCRzBEAiAJopaRrp3rkRwyFThM4feKs1/LrqP3oLj/4mxEWEX8NgIgWTtPIDgl2pOHAQHcW8Y8L3+kwPYbUfe5IHgE7Q49dpwBIQKU2GuOv3WiQjWM8riv3uJwUh3h+Zkco9yLJiHtQG5f4yb8A1JHQgO4L+Vt/X1AgPuVoNq3H6pwQ8kc7LO0s32H/CpvHVgwkSCzGylFw6I59wDVj85LlfJSDWRSj1ETsk8raL8NsrHYvAAiAgKrlm5OujbbSUWblgSIzODSGwdW09P6R+VSslYhsnsuUxioP8CcVgAAgAEAAIAAAACACQAAAAEAAAAAKfwGTE5QQlA0ALgv5W39fUCA+5Wg2rcfqnBDyRzss7SzfYf8Km8dWDCRIAjJeTBvKvCk+LhBj7FQmWMaz10SJxpnP3PjjR0/gGtNCfwGTE5QQlA0AQhTJmpjuc0zTwj8BU9QUkVUAAAI/AVPUFJFVAEgsk27k48Cfb3EwrfQs/IRgXwFDOjibiQyp3BA5/68Uy8A"
# }

psbt_signed="cHN...y8A"

bdk -n regtest wallet -w issuer -d "$DESC_TYPE($xpub_der_0)" -s "$ELECTRUM" \
    broadcast --psbt "$psbt_signed"
# example output:
# {
#   "txid": "f17d544c0ac161f758d379c4366e6ede8f394da9633671908738b415ae5c8fb4"
# }
Transaction confirmation

Now the transaction has been broadcast, let's confirm it:

bcli -rpcwallet=miner -generate 1

In real-world scenarios the parties wait for the transaction to be included in a block.

Receiver: accept transfer

Once the transaction has been confirmed, the receiver can accept the transfer, which is required to complete the transfer and update the contract state:

rgb1 accept "data1/$CONSIGNMENT"
# example output:
# Consignment is valid
#
# Transfer accepted into the stash

Note that accepting a transfer first validates its consignment.

Let's see the updated contract state, from the receiver's point of view:

rgb1 state "$contract_id" "$IFACE"
# example output:
# Global:
#   spec := (naming=(ticker=("USDT"), name=("USD Tether"), details=~), precision=0)
#   data := (terms=("demo RGB20 asset"), media=~)
#   issuedSupply := (1000)
#   created := (1691496693)
#
# Owned:
#   assetOwner:
#     amount=900, utxo=f17d544c0ac161f758d379c4366e6ede8f394da9633671908738b415ae5c8fb4:0, witness=f17d544c0ac161f758d379c4366e6ede8f394da9633671908738b415ae5c8fb4 # owner unknown
#     amount=100, utxo=bbc274a1f145552a6f22cab912c9b1903fb30333128b3b0f22212f2aa87772e2:0, witness=f17d544c0ac161f758d379c4366e6ede8f394da9633671908738b415ae5c8fb4 # owner unknown
#     amount=1000, utxo=6f6343401fc57c3f6a30043c61023e62311ee2b5d321823843af9cbcbfb2ac7e:1, witness=~ # owner unknown

The allocations for the original issuance and the transfer can be seen. The receiver can recognize its allocation from the utxo, which corresponds to the outpoint_receive provided to generate the invoice.

Sender: accept transfer

The sender doesn't need to explicitly accept the transfer, as it's automatically accepted when creating it.

The contract state already reflects the updated situation:

rgb0 state "$contract_id" "$IFACE"
# example output:
# Global:
#   spec := (naming=(ticker=("USDT"), name=("USD Tether"), details=~), precision=0)
#   data := (terms=("demo RGB20 asset"), media=~)
#   issuedSupply := (1000)
#   created := (1691496693)
#
# Owned:
#   assetOwner:
#     amount=900, utxo=f17d544c0ac161f758d379c4366e6ede8f394da9633671908738b415ae5c8fb4:0, witness=f17d544c0ac161f758d379c4366e6ede8f394da9633671908738b415ae5c8fb4 # owner unknown
#     amount=1000, utxo=6f6343401fc57c3f6a30043c61023e62311ee2b5d321823843af9cbcbfb2ac7e:1, witness=~ # owner unknown

Since the outpoint_receive was blinded during invoice generation, the payer has no information on where the asset was allocated by the transfer, so the receiver's allocation is not visible in the contract state on the sender's side.