diff --git a/packages/packages.json b/packages/packages.json index 13a13d126..0f80aebeb 100644 --- a/packages/packages.json +++ b/packages/packages.json @@ -1,20 +1,21 @@ { "dev": { "skill/valory/market_manager_abci/0.1.0": "bafybeif23nzty3mvhvx3tphgr3mdrfo4kadxzg4zi57at2pqvml5yrb2xa", - "skill/valory/decision_maker_abci/0.1.0": "bafybeiax6d4sdiytgolepg24mm7fp7q2p3tpbiqrp7j7vaogrnfiw3girm", - "skill/valory/trader_abci/0.1.0": "bafybeidf244v56ero6atrpo44ikboxivyqm6tl6wtlk2erpol6gwuphhha", + "skill/valory/decision_maker_abci/0.1.0": "bafybeidh3efbssuohhuku5b4go5jeqti4xv6dsdpaxm3ti4rudgqmyujpq", + "skill/valory/trader_abci/0.1.0": "bafybeifildb45hkpmusursxm3n7tg6bw42xfn4ssa2b7k73tu3og3nesze", "contract/valory/market_maker/0.1.0": "bafybeibgvm6jjrh26hvli3lqgi3xr7ihtjwu5pcbaeominq3w6zaksttle", - "agent/valory/trader/0.1.0": "bafybeids5ihwvaindgihdcrfu6lrkg6553zvu3xwhgswqjqmcbgsoxbjlq", - "service/valory/trader/0.1.0": "bafybeigq2dsu5wsqjkvbthhupv3k6rsoo2w37gy4foekhso26wykne5dku", + "agent/valory/trader/0.1.0": "bafybeihddqmhwhnelfclqocgahnbb4ipdhkatqsttl5acne62ifupfaw7i", + "service/valory/trader/0.1.0": "bafybeigd3mxyqb3koliro7oohkfegonqyz3bacihstytho2iazwcocahoq", "contract/valory/erc20/0.1.0": "bafybeichh4vg3mav7pkv4nymw4wpnnyhd6lm2uzmjpcsrqetxi2x4g7b2m", - "skill/valory/tx_settlement_multiplexer_abci/0.1.0": "bafybeidwafoxlqclwxssstd5sgtlr2aypg5nylyfn4q7f4tplt2rjlq6c4", + "skill/valory/tx_settlement_multiplexer_abci/0.1.0": "bafybeiesf2zztqhie7rpsrglddc7lbjfjxtybqsqhlmtp77ukddejrmiva", "contract/valory/mech/0.1.0": "bafybeigbpvjbdnxwwxlee47thv2xuwxk4qte7k6yxzuv4qlczl5ecjh6ie", "contract/valory/realitio/0.1.0": "bafybeic5ie4oodetj4krdogydvbfxg4qggc3matpiflocah626tpevpreq", "contract/valory/realitio_proxy/0.1.0": "bafybeidx37xzjjmapwacedgzhum6grfzhp5vhouz4zu3pvpgdy5pgb2fr4", "contract/valory/conditional_tokens/0.1.0": "bafybeigucumqbsk74nj4rpm4p2cpiky4dj6uws7nfmgpimuviaxcamwqnu", "contract/valory/agent_registry/0.1.0": "bafybeifdsvdensn52cngcwdcathmlu6vhsmzcljemfrb5uozsigzgwcvuy", "contract/valory/service_staking_token/0.1.0": "bafybeidzvc43ijt3rhmsbfkymtrzq6xcyo6gutp6uradivxjknlkany6xi", - "skill/valory/staking_abci/0.1.0": "bafybeifflsc3m5pvxrbhih4xclslhpqdnryilp72wudqffvvttyiung76u" + "skill/valory/staking_abci/0.1.0": "bafybeifflsc3m5pvxrbhih4xclslhpqdnryilp72wudqffvvttyiung76u", + "contract/valory/transfer_nft_condition/0.1.0": "bafybeig6j2tgy54mjpeqntrrcdzi32cogche57k4spsz6nz3yvouvb275u" }, "third_party": { "protocol/open_aea/signing/1.0.0": "bafybeihv62fim3wl2bayavfcg3u5e5cxu3b7brtu4cn5xoxd6lqwachasi", diff --git a/packages/valory/agents/trader/aea-config.yaml b/packages/valory/agents/trader/aea-config.yaml index bd327199c..166da0902 100644 --- a/packages/valory/agents/trader/aea-config.yaml +++ b/packages/valory/agents/trader/aea-config.yaml @@ -27,6 +27,7 @@ contracts: - valory/realitio_proxy:0.1.0:bafybeidx37xzjjmapwacedgzhum6grfzhp5vhouz4zu3pvpgdy5pgb2fr4 - valory/agent_registry:0.1.0:bafybeifdsvdensn52cngcwdcathmlu6vhsmzcljemfrb5uozsigzgwcvuy - valory/service_staking_token:0.1.0:bafybeidzvc43ijt3rhmsbfkymtrzq6xcyo6gutp6uradivxjknlkany6xi +- valory/transfer_nft_condition:0.1.0:bafybeig6j2tgy54mjpeqntrrcdzi32cogche57k4spsz6nz3yvouvb275u protocols: - open_aea/signing:1.0.0:bafybeihv62fim3wl2bayavfcg3u5e5cxu3b7brtu4cn5xoxd6lqwachasi - valory/abci:0.1.0:bafybeiaqmp7kocbfdboksayeqhkbrynvlfzsx4uy4x6nohywnmaig4an7u @@ -43,10 +44,10 @@ skills: - valory/reset_pause_abci:0.1.0:bafybeigkf7uh6zre3wc3btm2we7xffls4e4vurvtsou2nswbn6mcc3g52a - valory/termination_abci:0.1.0:bafybeif7dwj4i5okp7rsyeiyvnmt5xop7njvj27bmjqdx4skmimqls7t4e - valory/transaction_settlement_abci:0.1.0:bafybeic3ysdc46z4ipuonc2g6vdyqaxxljvfd45cflzi2xq7o7hre6lvvy -- valory/tx_settlement_multiplexer_abci:0.1.0:bafybeidwafoxlqclwxssstd5sgtlr2aypg5nylyfn4q7f4tplt2rjlq6c4 +- valory/tx_settlement_multiplexer_abci:0.1.0:bafybeiesf2zztqhie7rpsrglddc7lbjfjxtybqsqhlmtp77ukddejrmiva - valory/market_manager_abci:0.1.0:bafybeif23nzty3mvhvx3tphgr3mdrfo4kadxzg4zi57at2pqvml5yrb2xa -- valory/decision_maker_abci:0.1.0:bafybeiax6d4sdiytgolepg24mm7fp7q2p3tpbiqrp7j7vaogrnfiw3girm -- valory/trader_abci:0.1.0:bafybeidf244v56ero6atrpo44ikboxivyqm6tl6wtlk2erpol6gwuphhha +- valory/decision_maker_abci:0.1.0:bafybeidh3efbssuohhuku5b4go5jeqti4xv6dsdpaxm3ti4rudgqmyujpq +- valory/trader_abci:0.1.0:bafybeifildb45hkpmusursxm3n7tg6bw42xfn4ssa2b7k73tu3og3nesze - valory/staking_abci:0.1.0:bafybeifflsc3m5pvxrbhih4xclslhpqdnryilp72wudqffvvttyiung76u default_ledger: ethereum required_ledgers: @@ -192,6 +193,16 @@ models: "openai-gpt-3.5-turbo", "openai-gpt-4", "stabilityai-stable-diffusion-v1-5", "stabilityai-stable-diffusion-xl-beta-v2-2-2", "stabilityai-stable-diffusion-512-v2-1", "stabilityai-stable-diffusion-768-v2-1"]} + use_nevermined: ${bool:true} + mech_to_subscription_params: ${list:[["base_url", "https://marketplace-api.gnosis.nevermined.app/api/v1/metadata/assets/ddo"], + ["did", "did:nv:416e35cb209ecbfbf23e1192557b06e94c5d9a9afb025cce2e9baff23e907195"], + ["escrow_payment_condition_address", "0x9dDC4F1Ea5b94C138A23b60EC48c0d01d172629a"], + ["lock_payment_condition_address", "0xDE85A368Ee6f374d236500d176814365370778dA"], + ["transfer_nft_condition_address", "0xbBa4A25262745a55f020D0a3E9a82c25bb6F4979"], + ["token_address", "0xa30DE8C6aC39B825192e5F1FADe0770332D279A8"], ["order_address", + "0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4"], ["nft_amount", "1"], ["payment_token", + "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83"], ["order_address", "0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4"], + ["price", "0"]]} staking_contract_address: ${str:0x5add592ce0a1B5DceCebB5Dcac086Cd9F9e3eA5C} agent_balance_threshold: ${int:10000000000000000} refill_check_interval: ${int:10} diff --git a/packages/valory/contracts/transfer_nft_condition/README.md b/packages/valory/contracts/transfer_nft_condition/README.md new file mode 100644 index 000000000..e4d57e82f --- /dev/null +++ b/packages/valory/contracts/transfer_nft_condition/README.md @@ -0,0 +1 @@ +# TRANSFER_NFT_CONDITION token contract diff --git a/packages/valory/contracts/transfer_nft_condition/__init__.py b/packages/valory/contracts/transfer_nft_condition/__init__.py new file mode 100644 index 000000000..d8773d74b --- /dev/null +++ b/packages/valory/contracts/transfer_nft_condition/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023-2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the support resources for TransferNFTCondition.""" diff --git a/packages/valory/contracts/transfer_nft_condition/build/TransferNFTCondition.json b/packages/valory/contracts/transfer_nft_condition/build/TransferNFTCondition.json new file mode 100644 index 000000000..efb696034 --- /dev/null +++ b/packages/valory/contracts/transfer_nft_condition/build/TransferNFTCondition.json @@ -0,0 +1,1105 @@ +{ + "_format": "", + "contractName": "", + "sourceName": "", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "_agreementId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "_did", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "_receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "_conditionId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "Fulfilled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_id", + "type": "bytes32" + } + ], + "name": "abortByTimeOut", + "outputs": [ + { + "internalType": "enum ConditionStoreLibrary.ConditionState", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addr", + "type": "address" + } + ], + "name": "addressToBytes32", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_b32", + "type": "bytes32" + } + ], + "name": "bytes32ToAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "_amounts", + "type": "uint256[]" + } + ], + "name": "calculateTotalAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_did", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftHolder", + "type": "address" + }, + { + "internalType": "address", + "name": "_nftReceiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nftAmount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_lockPaymentCondition", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftContractAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "_transfer", + "type": "bool" + } + ], + "name": "encodeParams", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_agreementId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_did", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftReceiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nftAmount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_lockPaymentCondition", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftContractAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "_transfer", + "type": "bool" + } + ], + "name": "fulfill", + "outputs": [ + { + "internalType": "enum ConditionStoreLibrary.ConditionState", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_agreementId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_did", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftReceiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nftAmount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_lockPaymentCondition", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftContractAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "_transfer", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "_expirationBlock", + "type": "uint256" + } + ], + "name": "fulfill", + "outputs": [ + { + "internalType": "enum ConditionStoreLibrary.ConditionState", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_agreementId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_did", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftReceiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nftAmount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_lockPaymentCondition", + "type": "bytes32" + } + ], + "name": "fulfill", + "outputs": [ + { + "internalType": "enum ConditionStoreLibrary.ConditionState", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_agreementId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_did", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftHolder", + "type": "address" + }, + { + "internalType": "address", + "name": "_nftReceiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nftAmount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_lockPaymentCondition", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "_transfer", + "type": "bool" + } + ], + "name": "fulfillForDelegate", + "outputs": [ + { + "internalType": "enum ConditionStoreLibrary.ConditionState", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_agreementId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_did", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftHolder", + "type": "address" + }, + { + "internalType": "address", + "name": "_nftReceiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nftAmount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_lockPaymentCondition", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftContractAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "_transfer", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "_expirationBlock", + "type": "uint256" + } + ], + "name": "fulfillForDelegate", + "outputs": [ + { + "internalType": "enum ConditionStoreLibrary.ConditionState", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_agreementId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_params", + "type": "bytes" + } + ], + "name": "fulfillProxy", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_agreementId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_valueHash", + "type": "bytes32" + } + ], + "name": "generateId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNFTDefaultAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNvmConfigAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTrustedForwarder", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nftContractAddress", + "type": "address" + } + ], + "name": "grantMarketRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "grantProxyRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "name": "hasNVMOperatorRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_did", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftHolder", + "type": "address" + }, + { + "internalType": "address", + "name": "_nftReceiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nftAmount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_lockCondition", + "type": "bytes32" + } + ], + "name": "hashValues", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_did", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftHolder", + "type": "address" + }, + { + "internalType": "address", + "name": "_nftReceiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nftAmount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_lockCondition", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_nftContractAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "_transfer", + "type": "bool" + } + ], + "name": "hashValues", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "address", + "name": "_conditionStoreManagerAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_didRegistryAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_ercAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_nftContractAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "isContract", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "forwarder", + "type": "address" + } + ], + "name": "isTrustedForwarder", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nftContractAddress", + "type": "address" + } + ], + "name": "revokeMarketRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "revokeProxyRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_id", + "type": "bytes32" + }, + { + "name": "_did", + "type": "bytes32" + }, + { + "name": "_conditionIds", + "type": "bytes32[]" + }, + { + "name": "_timeLocks", + "type": "uint256[]" + }, + { + "name": "_timeOuts", + "type": "uint256[]" + }, + { + "name": "_accessConsumer", + "type": "address" + }, + { + "name": "_idx", + "type": "uint256" + }, + { + "name": "_rewardAddress", + "type": "address" + }, + { + "name": "_tokenAddress", + "type": "address" + }, + { + "name": "_amounts", + "type": "uint256[]" + }, + { + "name": "_receivers", + "type": "address[]" + } + ], + "name": "createAgreementAndPayEscrow", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "", + "deployedBytecode": "", + "linkReferences": {}, + "deployedLinkReferences": {} +} \ No newline at end of file diff --git a/packages/valory/contracts/transfer_nft_condition/contract.py b/packages/valory/contracts/transfer_nft_condition/contract.py new file mode 100644 index 000000000..1fee90e96 --- /dev/null +++ b/packages/valory/contracts/transfer_nft_condition/contract.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023-2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the class to connect to an TRANSFER_NFT_CONDITION token contract.""" + +from typing import Dict, List + +from aea.common import JSONLike +from aea.configurations.base import PublicId +from aea.contracts.base import Contract +from aea.crypto.base import LedgerApi +from aea_ledger_ethereum import EthereumApi +from web3 import Web3 + +PUBLIC_ID = PublicId.from_str("valory/transfer_nft_condition:0.1.0") + + +class TransferNftCondition(Contract): + """The TransferNftCondition contract.""" + + contract_id = PUBLIC_ID + + @classmethod + def build_order_tx( + cls, + ledger_api: LedgerApi, + contract_address: str, + agreement_id: str, + did: str, + condition_ids: List[str], + time_locks: List[int], + time_outs: List[int], + consumer: str, + index: int, + reward_address: str, + token_address: str, + amounts: List[int], + receives: List[str], + ) -> Dict[str, bytes]: + """Build an TransferNftCondition approval.""" + contract_instance = cls.get_instance(ledger_api, contract_address) + data = contract_instance.encodeABI("createAgreementAndPayEscrow", args=( + bytes.fromhex(agreement_id[2:]), + bytes.fromhex(did[2:]), + [bytes.fromhex(condition_id[2:]) for condition_id in condition_ids], + time_locks, + time_outs, + Web3.to_checksum_address(consumer), + index, + Web3.to_checksum_address(reward_address), + Web3.to_checksum_address(token_address), + amounts, + [Web3.to_checksum_address(receive) for receive in receives], + )) + return {"data": bytes.fromhex(data[2:])} + + + @classmethod + def balance_of( + cls, + ledger_api: LedgerApi, + contract_address: str, + address: str, + did: str, + ) -> JSONLike: + """Get the balance of an address.""" + contract_instance = cls.get_instance(ledger_api, contract_address) + balance = contract_instance.functions.balanceOf( + Web3.to_checksum_address(address), + int(did, 16) + ).call() + return dict(data=balance) diff --git a/packages/valory/contracts/transfer_nft_condition/contract.yaml b/packages/valory/contracts/transfer_nft_condition/contract.yaml new file mode 100644 index 000000000..176ca22cb --- /dev/null +++ b/packages/valory/contracts/transfer_nft_condition/contract.yaml @@ -0,0 +1,32 @@ +name: transfer_nft_condition +author: valory +version: 0.1.0 +type: contract +description: TRANSFER_NFT_CONDITION token contract +license: Apache-2.0 +aea_version: '>=1.0.0, <2.0.0' +fingerprint: + README.md: bafybeidiaep6cpoqlabnajvsgegpt72hqnbtozayicaxclzxzftopvlw5u + __init__.py: bafybeigz4j3ow7nagttswpbrpajnxqtxrl35oznejzxwi6bhfdeltnab5i + build/TransferNFTCondition.json: bafybeicovauoyn5bulnmz3ulkee56s46viemw3vpy7fk3zi4isol2jkdca + contract.py: bafybeiamwqz5tln7xknhoogsev36c3vhi4u4aovbwmowvpavhmzbtp7ywy +fingerprint_ignore_patterns: [] +contracts: [] +class_name: TransferNftCondition +contract_interface_paths: + ethereum: build/TransferNFTCondition.json +dependencies: + ecdsa: + version: '>=0.15' + eth_typing: {} + hexbytes: {} + open-aea-ledger-ethereum: + version: ==1.44.0 + open-aea-test-autonomy: + version: ==0.14.0 + packaging: {} + py-eth-sig-utils: {} + requests: + version: ==2.28.1 + web3: + version: <7,>=6.0.0 diff --git a/packages/valory/services/trader/service.yaml b/packages/valory/services/trader/service.yaml index cc348b761..7213f0574 100644 --- a/packages/valory/services/trader/service.yaml +++ b/packages/valory/services/trader/service.yaml @@ -7,7 +7,7 @@ license: Apache-2.0 fingerprint: README.md: bafybeigtuothskwyvrhfosps2bu6suauycolj67dpuxqvnicdrdu7yhtvq fingerprint_ignore_patterns: [] -agent: valory/trader:0.1.0:bafybeids5ihwvaindgihdcrfu6lrkg6553zvu3xwhgswqjqmcbgsoxbjlq +agent: valory/trader:0.1.0:bafybeihddqmhwhnelfclqocgahnbb4ipdhkatqsttl5acne62ifupfaw7i number_of_agents: 4 deployment: {} --- @@ -113,6 +113,16 @@ type: skill file_hash_to_strategies_json: ${FILE_HASH_TO_STRATEGIES_JSON:list:[["bafybeia4m2mzhedzjbm3enmhgd6csd6hredgwsgpgs2wm2j43vodeqxjfy",["bet_amount_per_threshold"]],["bafybeibtbw44oslyxqrgpnu644zcyc763phtr4rsglxb4b4ma4ha3k5kyq",["kelly_criterion"]]]} strategies_kwargs: ${STRATEGIES_KWARGS:list:[["bet_kelly_fraction",0.5],["floor_balance",500000000000000000],["bet_amount_per_threshold",{"0.0":0,"0.1":0,"0.2":0,"0.3":0,"0.4":0,"0.5":0,"0.6":60000000000000000,"0.7":90000000000000000,"0.8":100000000000000000,"0.9":1000000000000000000,"1.0":10000000000000000000}]]} use_subgraph_for_redeeming: ${USE_SUBGRAPH_FOR_REDEEMING:bool:true} + use_nevermined: ${USE_NEVERMINED:bool:false} + mech_to_subscription_params: ${SUBSCRIPTION_PARAMS:list:[["base_url", "https://marketplace-api.gnosis.nevermined.app/api/v1/metadata/assets/ddo"], + ["did", "did:nv:416e35cb209ecbfbf23e1192557b06e94c5d9a9afb025cce2e9baff23e907195"], + ["escrow_payment_condition_address", "0x9dDC4F1Ea5b94C138A23b60EC48c0d01d172629a"], + ["lock_payment_condition_address", "0xDE85A368Ee6f374d236500d176814365370778dA"], + ["transfer_nft_condition_address", "0xbBa4A25262745a55f020D0a3E9a82c25bb6F4979"], + ["token_address", "0xa30DE8C6aC39B825192e5F1FADe0770332D279A8"], ["order_address", + "0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4"], ["nft_amount", "1"], ["payment_token", + "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83"], ["order_address", "0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4"], + ["price", "0"]]} benchmark_tool: &id004 args: log_dir: ${LOG_DIR:str:/benchmarks} @@ -197,6 +207,16 @@ type: skill file_hash_to_strategies_json: ${FILE_HASH_TO_STRATEGIES_JSON:list:[["bafybeia4m2mzhedzjbm3enmhgd6csd6hredgwsgpgs2wm2j43vodeqxjfy",["bet_amount_per_threshold"]],["bafybeibtbw44oslyxqrgpnu644zcyc763phtr4rsglxb4b4ma4ha3k5kyq",["kelly_criterion"]]]} strategies_kwargs: ${STRATEGIES_KWARGS:list:[["bet_kelly_fraction",0.5],["floor_balance",500000000000000000],["bet_amount_per_threshold",{"0.0":0,"0.1":0,"0.2":0,"0.3":0,"0.4":0,"0.5":0,"0.6":60000000000000000,"0.7":90000000000000000,"0.8":100000000000000000,"0.9":1000000000000000000,"1.0":10000000000000000000}]]} use_subgraph_for_redeeming: ${USE_SUBGRAPH_FOR_REDEEMING:bool:true} + use_nevermined: ${USE_NEVERMINED:bool:false} + mech_to_subscription_params: ${SUBSCRIPTION_PARAMS:list:[["base_url", "https://marketplace-api.gnosis.nevermined.app/api/v1/metadata/assets/ddo"], + ["did", "did:nv:416e35cb209ecbfbf23e1192557b06e94c5d9a9afb025cce2e9baff23e907195"], + ["escrow_payment_condition_address", "0x9dDC4F1Ea5b94C138A23b60EC48c0d01d172629a"], + ["lock_payment_condition_address", "0xDE85A368Ee6f374d236500d176814365370778dA"], + ["transfer_nft_condition_address", "0xbBa4A25262745a55f020D0a3E9a82c25bb6F4979"], + ["token_address", "0xa30DE8C6aC39B825192e5F1FADe0770332D279A8"], ["order_address", + "0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4"], ["nft_amount", "1"], ["payment_token", + "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83"], ["order_address", "0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4"], + ["price", "0"]]} benchmark_tool: *id004 2: models: @@ -279,6 +299,16 @@ type: skill file_hash_to_strategies_json: ${FILE_HASH_TO_STRATEGIES_JSON:list:[["bafybeia4m2mzhedzjbm3enmhgd6csd6hredgwsgpgs2wm2j43vodeqxjfy",["bet_amount_per_threshold"]],["bafybeibtbw44oslyxqrgpnu644zcyc763phtr4rsglxb4b4ma4ha3k5kyq",["kelly_criterion"]]]} strategies_kwargs: ${STRATEGIES_KWARGS:list:[["bet_kelly_fraction",0.5],["floor_balance",500000000000000000],["bet_amount_per_threshold",{"0.0":0,"0.1":0,"0.2":0,"0.3":0,"0.4":0,"0.5":0,"0.6":60000000000000000,"0.7":90000000000000000,"0.8":100000000000000000,"0.9":1000000000000000000,"1.0":10000000000000000000}]]} use_subgraph_for_redeeming: ${USE_SUBGRAPH_FOR_REDEEMING:bool:true} + use_nevermined: ${USE_NEVERMINED:bool:false} + mech_to_subscription_params: ${SUBSCRIPTION_PARAMS:list:[["base_url", "https://marketplace-api.gnosis.nevermined.app/api/v1/metadata/assets/ddo"], + ["did", "did:nv:416e35cb209ecbfbf23e1192557b06e94c5d9a9afb025cce2e9baff23e907195"], + ["escrow_payment_condition_address", "0x9dDC4F1Ea5b94C138A23b60EC48c0d01d172629a"], + ["lock_payment_condition_address", "0xDE85A368Ee6f374d236500d176814365370778dA"], + ["transfer_nft_condition_address", "0xbBa4A25262745a55f020D0a3E9a82c25bb6F4979"], + ["token_address", "0xa30DE8C6aC39B825192e5F1FADe0770332D279A8"], ["order_address", + "0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4"], ["nft_amount", "1"], ["payment_token", + "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83"], ["order_address", "0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4"], + ["price", "0"]]} benchmark_tool: *id004 3: models: @@ -361,6 +391,16 @@ type: skill file_hash_to_strategies_json: ${FILE_HASH_TO_STRATEGIES_JSON:list:[["bafybeia4m2mzhedzjbm3enmhgd6csd6hredgwsgpgs2wm2j43vodeqxjfy",["bet_amount_per_threshold"]],["bafybeibtbw44oslyxqrgpnu644zcyc763phtr4rsglxb4b4ma4ha3k5kyq",["kelly_criterion"]]]} strategies_kwargs: ${STRATEGIES_KWARGS:list:[["bet_kelly_fraction",0.5],["floor_balance",500000000000000000],["bet_amount_per_threshold",{"0.0":0,"0.1":0,"0.2":0,"0.3":0,"0.4":0,"0.5":0,"0.6":60000000000000000,"0.7":90000000000000000,"0.8":100000000000000000,"0.9":1000000000000000000,"1.0":10000000000000000000}]]} use_subgraph_for_redeeming: ${USE_SUBGRAPH_FOR_REDEEMING:bool:true} + use_nevermined: ${USE_NEVERMINED:bool:false} + mech_to_subscription_params: ${SUBSCRIPTION_PARAMS:list:[["base_url", "https://marketplace-api.gnosis.nevermined.app/api/v1/metadata/assets/ddo"], + ["did", "did:nv:416e35cb209ecbfbf23e1192557b06e94c5d9a9afb025cce2e9baff23e907195"], + ["escrow_payment_condition_address", "0x9dDC4F1Ea5b94C138A23b60EC48c0d01d172629a"], + ["lock_payment_condition_address", "0xDE85A368Ee6f374d236500d176814365370778dA"], + ["transfer_nft_condition_address", "0xbBa4A25262745a55f020D0a3E9a82c25bb6F4979"], + ["token_address", "0xa30DE8C6aC39B825192e5F1FADe0770332D279A8"], ["order_address", + "0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4"], ["nft_amount", "1"], ["payment_token", + "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83"], ["order_address", "0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4"], + ["price", "0"]]} benchmark_tool: *id004 --- public_id: valory/ledger:0.19.0 diff --git a/packages/valory/skills/decision_maker_abci/behaviours/base.py b/packages/valory/skills/decision_maker_abci/behaviours/base.py index 536d41fc8..1954ebbb1 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/base.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/base.py @@ -20,6 +20,7 @@ """This module contains the base behaviour for the 'decision_maker_abci' skill.""" import dataclasses +import json from abc import ABC from datetime import datetime, timedelta from typing import Any, Callable, Dict, Generator, List, Optional, Set, Tuple, cast @@ -555,3 +556,97 @@ def finish_behaviour(self, payload: BaseTxPayload) -> Generator: yield from self.wait_until_round_end() self.set_done() + + +class BaseSubscriptionBehaviour(DecisionMakerBaseBehaviour, ABC): + """Base class for subscription behaviours.""" + + @property + def subscription_params(self) -> Dict[str, Any]: + """Get the subscription params.""" + return self.params.mech_to_subscription_params + + @property + def did(self) -> str: + """Get the did.""" + subscription_params = self.subscription_params + return subscription_params["did"] + + @property + def escrow_payment_condition_address(self) -> str: + """Get the escrow payment address.""" + subscription_params = self.subscription_params + return subscription_params["escrow_payment_condition_address"] + + @property + def lock_payment_condition_address(self) -> str: + """Get the lock payment address.""" + subscription_params = self.subscription_params + return subscription_params["lock_payment_condition_address"] + + @property + def transfer_nft_condition_address(self) -> str: + """Get the transfer nft condition address.""" + subscription_params = self.subscription_params + return subscription_params["transfer_nft_condition_address"] + + @property + def token_address(self) -> str: + """Get the token address.""" + subscription_params = self.subscription_params + return subscription_params["token_address"] + + @property + def order_address(self) -> str: + """Get the order address.""" + subscription_params = self.subscription_params + return subscription_params["order_address"] + + @property + def purchase_amount(self) -> int: + """Get the purchase amount.""" + subscription_params = self.subscription_params + return int(subscription_params["nft_amount"]) + + @property + def price(self) -> int: + """Get the price.""" + subscription_params = self.subscription_params + return int(subscription_params["price"]) + + @property + def payment_token(self) -> str: + """Get the payment token.""" + subscription_params = self.subscription_params + return subscription_params["payment_token"] + + @property + def base_url(self) -> str: + """Get the base url.""" + subscription_params = self.subscription_params + return subscription_params["base_url"] + + def _resolve_did(self) -> Generator[None, None, Optional[Dict[str, Any]]]: + """Resolve and parse the did.""" + did_url = f"{self.base_url}/{self.did}" + response = yield from self.get_http_response( + method="GET", + url=did_url, + headers={"accept": "application/json"}, + ) + if response.status_code != 200: + self.context.logger.error( + f"Could not retrieve data from did url {did_url}. " + f"Received status code {response.status_code}." + ) + return None + try: + data = json.loads(response.body) + except (ValueError, TypeError) as e: + self.context.logger.error( + f"Could not parse response from nervermined api, " + f"the following error was encountered {type(e).__name__}: {e}" + ) + return None + + return data diff --git a/packages/valory/skills/decision_maker_abci/behaviours/claim_subscription.py b/packages/valory/skills/decision_maker_abci/behaviours/claim_subscription.py new file mode 100644 index 000000000..049e8edb3 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/claim_subscription.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the behaviour for the decision-making of the skill.""" +import json +from typing import Any, Generator + +from packages.valory.skills.decision_maker_abci.behaviours.base import ( + BaseSubscriptionBehaviour, +) +from packages.valory.skills.decision_maker_abci.payloads import ClaimPayload +from packages.valory.skills.decision_maker_abci.states.claim_subscription import ( + ClaimRound, +) +from packages.valory.skills.decision_maker_abci.utils.nevermined import ( + get_claim_endpoint, + get_creator, +) + + +SERVICE_INDEX = -1 +ERC1155 = 1155 + + +class ClaimSubscriptionBehaviour(BaseSubscriptionBehaviour): + """A behaviour in which the agents claim the subscription they purchased.""" + + matching_round = ClaimRound + + def __init__(self, **kwargs: Any) -> None: + """Initialize `RedeemBehaviour`.""" + super().__init__(**kwargs) + self.order_tx: str = "" + self.approval_tx: str = "" + self.balance: int = 0 + + def _claim_subscription(self) -> Generator[None, None, bool]: + """Claim the subscription.""" + did_doc = yield from self._resolve_did() + if did_doc is None: + return False + + creator = get_creator(did_doc) + claim_endpoint = get_claim_endpoint(did_doc) + body = { + "agreementId": self.synchronized_data.agreement_id, + "did": self.did, + "nftHolder": creator, + "nftReceiver": self.synchronized_data.safe_contract_address, + "nftAmount": str(self.purchase_amount), + "nftType": ERC1155, + "serviceIndex": SERVICE_INDEX, + } + res = yield from self.get_http_response( + "POST", + claim_endpoint, + json.dumps(body).encode(), + headers={"Content-Type": "application/json"}, + ) + if res.status_code != 201: + self.context.logger.warning( + f"Failed to claim subscription: {res.status_code!r} - {res.body!r}", + ) + return False + + return True + + def async_act(self) -> Generator: + """Do the action.""" + + with self.context.benchmark_tool.measure(self.behaviour_id).local(): + claim = yield from self._claim_subscription() + sender = self.context.agent_address + payload = ClaimPayload( + sender, + vote=claim, + ) + yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/order_subscription.py b/packages/valory/skills/decision_maker_abci/behaviours/order_subscription.py new file mode 100644 index 000000000..190682376 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/order_subscription.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the behaviour for the decision-making of the skill.""" +from typing import Any, Dict, Generator, List, Optional, cast + +from hexbytes import HexBytes + +from packages.valory.contracts.erc20.contract import ERC20 +from packages.valory.contracts.transfer_nft_condition.contract import ( + TransferNftCondition, +) +from packages.valory.protocols.contract_api import ContractApiMessage +from packages.valory.skills.decision_maker_abci.behaviours.base import ( + BaseSubscriptionBehaviour, +) +from packages.valory.skills.decision_maker_abci.models import MultisendBatch +from packages.valory.skills.decision_maker_abci.payloads import SubscriptionPayload +from packages.valory.skills.decision_maker_abci.states.order_subscription import ( + SubscriptionRound, +) +from packages.valory.skills.decision_maker_abci.utils.nevermined import ( + generate_id, + get_agreement_id, + get_escrow_payment_seed, + get_lock_payment_seed, + get_price, + get_timeouts_and_timelocks, + get_transfer_nft_condition_seed, + no_did_prefixed, + zero_x_transformer, +) + + +LOCK_CONDITION_INDEX = 0 + + +class OrderSubscriptionBehaviour(BaseSubscriptionBehaviour): + """A behaviour in which the agents purchase a subscriptions.""" + + matching_round = SubscriptionRound + + def __init__(self, **kwargs: Any) -> None: + """Initialize `RedeemBehaviour`.""" + super().__init__(**kwargs) + self.order_tx: str = "" + self.approval_tx: str = "" + self.balance: int = 0 + self.agreement_id: str = "" + + def _get_condition_ids( + self, agreement_id_seed: str, did_doc: Dict[str, Any] + ) -> List[str]: + """Get the condition ids.""" + self.agreement_id = get_agreement_id( + agreement_id_seed, self.synchronized_data.safe_contract_address + ) + price = get_price(did_doc) + receivers = list(price.keys()) + amounts = list(price.values()) + lock_payment_seed, lock_payment_id = get_lock_payment_seed( + self.agreement_id, + did_doc, + self.lock_payment_condition_address, + self.escrow_payment_condition_address, + self.payment_token, + amounts, + receivers, + ) + ( + transfer_nft_condition_seed, + transfer_nft_condition_id, + ) = get_transfer_nft_condition_seed( + self.agreement_id, + did_doc, + self.synchronized_data.safe_contract_address, + self.purchase_amount, + self.transfer_nft_condition_address, + lock_payment_id, + self.token_address, + ) + escrow_payment_seed, _ = get_escrow_payment_seed( + self.agreement_id, + did_doc, + amounts, + receivers, + self.synchronized_data.safe_contract_address, + self.escrow_payment_condition_address, + self.payment_token, + lock_payment_id, + transfer_nft_condition_id, + ) + condition_ids = [ + lock_payment_seed, + transfer_nft_condition_seed, + escrow_payment_seed, + ] + return condition_ids + + def _get_purchase_params(self) -> Generator[None, None, Optional[Dict[str, Any]]]: + """Get purchase params.""" + agreement_id = zero_x_transformer(generate_id()) + did = zero_x_transformer(no_did_prefixed(self.did)) + did_doc = yield from self._resolve_did() + if did_doc is None: + # something went wrong + return None + condition_ids = self._get_condition_ids(agreement_id, did_doc) + timeouts, timelocks = get_timeouts_and_timelocks(did_doc) + price = get_price(did_doc) + receivers = list(price.keys()) + amounts = list(price.values()) + + return { + "agreement_id": agreement_id, + "did": did, + "condition_ids": condition_ids, + "consumer": self.synchronized_data.safe_contract_address, + "index": LOCK_CONDITION_INDEX, + "time_outs": timeouts, + "time_locks": timelocks, + "reward_address": self.escrow_payment_condition_address, + "receivers": receivers, + "amounts": amounts, + "contract_address": self.order_address, + "token_address": self.payment_token, + } + + def _get_approval_params(self) -> Dict[str, Any]: + """Get approval params.""" + approval_params = {} + approval_params["token"] = self.payment_token + approval_params["spender"] = self.lock_payment_condition_address + approval_params["amount"] = self.price # type: ignore + return approval_params + + def _prepare_order_tx( + self, + contract_address: str, + agreement_id: str, + did: str, + condition_ids: List[str], + time_locks: List[int], + time_outs: List[int], + consumer: str, + index: int, + reward_address: str, + token_address: str, + amounts: List[int], + receivers: List[str], + ) -> Generator[None, None, bool]: + """Prepare a purchase tx.""" + result = yield from self.contract_interact( + performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, # type: ignore + contract_address=contract_address, + contract_public_id=TransferNftCondition.contract_id, + contract_callable="build_order_tx", + data_key="data", + placeholder="order_tx", + agreement_id=agreement_id, + did=did, + condition_ids=condition_ids, + time_locks=time_locks, + time_outs=time_outs, + consumer=consumer, + index=index, + reward_address=reward_address, + token_address=token_address, + amounts=amounts, + receives=receivers, + ) + if not result: + return False + + self.multisend_batches.append( + MultisendBatch( + to=contract_address, + data=HexBytes(self.order_tx), + ) + ) + return True + + def _prepare_approval_tx( + self, token: str, spender: str, amount: int + ) -> Generator[None, None, bool]: + """Prepare an approval tx.""" + result = yield from self.contract_interact( + performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, # type: ignore + contract_address=token, + contract_public_id=ERC20.contract_id, + contract_callable="build_approval_tx", + data_key="data", + placeholder="approval_tx", + amount=amount, + spender=spender, + ) + if not result: + return False + + self.multisend_batches.append( + MultisendBatch( + to=token, + data=HexBytes(self.approval_tx), + ) + ) + return True + + def _get_balance( + self, token: str, address: str, did: str + ) -> Generator[None, None, bool]: + """Prepare an approval tx.""" + result = yield from self.contract_interact( + performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, # type: ignore + contract_address=token, + contract_public_id=TransferNftCondition.contract_id, + contract_callable="balance_of", + data_key="data", + placeholder="balance", + address=address, + did=did, + ) + if not result: + return False + return True + + def _should_purchase(self) -> Generator[None, None, bool]: + """Check if the subscription should be purchased.""" + if not self.params.use_nevermined: + self.context.logger.info("Nevermined subscriptions are turned off.") + return False + + result = yield from self._get_balance( + self.token_address, + self.synchronized_data.safe_contract_address, + zero_x_transformer(no_did_prefixed(self.did)), + ) + if not result: + self.context.logger.warning("Failed to get balance") + return False + + return self.balance <= 0 + + def get_payload_content(self) -> Generator[None, None, str]: + """Get the payload.""" + should_purchase = yield from self._should_purchase() + if not should_purchase: + return SubscriptionRound.NO_TX_PAYLOAD + + approval_params = self._get_approval_params() + result = yield from self._prepare_approval_tx(**approval_params) + if not result: + return SubscriptionRound.ERROR_PAYLOAD + + purchase_params = yield from self._get_purchase_params() + if purchase_params is None: + return SubscriptionRound.ERROR_PAYLOAD + + result = yield from self._prepare_order_tx(**purchase_params) + if not result: + return SubscriptionRound.ERROR_PAYLOAD + + for build_step in ( + self._build_multisend_data, + self._build_multisend_safe_tx_hash, + ): + yield from self.wait_for_condition_with_sleep(build_step) + + return cast(str, self.tx_hex) + + def async_act(self) -> Generator: + """Do the action.""" + + with self.context.benchmark_tool.measure(self.behaviour_id).local(): + payload_data = yield from self.get_payload_content() + sender = self.context.agent_address + payload = SubscriptionPayload( + sender, + tx_submitter=SubscriptionRound.auto_round_id(), + tx_hash=payload_data, + agreement_id=self.agreement_id, + ) + yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py index 1bf532ddf..0ad8289ad 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py @@ -31,6 +31,9 @@ from packages.valory.skills.decision_maker_abci.behaviours.blacklisting import ( BlacklistingBehaviour, ) +from packages.valory.skills.decision_maker_abci.behaviours.claim_subscription import ( + ClaimSubscriptionBehaviour, +) from packages.valory.skills.decision_maker_abci.behaviours.decision_receive import ( DecisionReceiveBehaviour, ) @@ -40,6 +43,9 @@ from packages.valory.skills.decision_maker_abci.behaviours.handle_failed_tx import ( HandleFailedTxBehaviour, ) +from packages.valory.skills.decision_maker_abci.behaviours.order_subscription import ( + OrderSubscriptionBehaviour, +) from packages.valory.skills.decision_maker_abci.behaviours.randomness import ( RandomnessBehaviour, ) @@ -67,5 +73,7 @@ class AgentDecisionMakerRoundBehaviour(AbstractRoundBehaviour): RedeemBehaviour, # type: ignore HandleFailedTxBehaviour, # type: ignore ToolSelectionBehaviour, # type: ignore + OrderSubscriptionBehaviour, + ClaimSubscriptionBehaviour, RandomnessBehaviour, # type: ignore } diff --git a/packages/valory/skills/decision_maker_abci/fsm_specification.yaml b/packages/valory/skills/decision_maker_abci/fsm_specification.yaml index 6d346c72a..d6d2314af 100644 --- a/packages/valory/skills/decision_maker_abci/fsm_specification.yaml +++ b/packages/valory/skills/decision_maker_abci/fsm_specification.yaml @@ -8,20 +8,24 @@ alphabet_in: - NO_MAJORITY - NO_OP - NO_REDEEMING +- NO_SUBSCRIPTION - REDEEM_ROUND_TIMEOUT - ROUND_TIMEOUT - SLOTS_UNSUPPORTED_ERROR +- SUBSCRIPTION_ERROR - TIE - UNPROFITABLE default_start_state: SamplingRound final_states: - FinishedDecisionMakerRound +- FinishedSubscriptionRound - FinishedWithoutDecisionRound - FinishedWithoutRedeemingRound - ImpossibleRound - RefillRequiredRound label: DecisionMakerAbciApp start_states: +- ClaimRound - DecisionReceiveRound - HandleFailedTxRound - RedeemRound @@ -29,9 +33,11 @@ start_states: states: - BetPlacementRound - BlacklistingRound +- ClaimRound - DecisionReceiveRound - DecisionRequestRound - FinishedDecisionMakerRound +- FinishedSubscriptionRound - FinishedWithoutDecisionRound - FinishedWithoutRedeemingRound - HandleFailedTxRound @@ -40,6 +46,7 @@ states: - RedeemRound - RefillRequiredRound - SamplingRound +- SubscriptionRound - ToolSelectionRound transition_func: (BetPlacementRound, DONE): FinishedDecisionMakerRound @@ -52,6 +59,10 @@ transition_func: (BlacklistingRound, NONE): ImpossibleRound (BlacklistingRound, NO_MAJORITY): BlacklistingRound (BlacklistingRound, ROUND_TIMEOUT): BlacklistingRound + (ClaimRound, DONE): RandomnessRound + (ClaimRound, NO_MAJORITY): ClaimRound + (ClaimRound, ROUND_TIMEOUT): ClaimRound + (ClaimRound, SUBSCRIPTION_ERROR): ClaimRound (DecisionReceiveRound, DONE): BetPlacementRound (DecisionReceiveRound, MECH_RESPONSE_ERROR): BlacklistingRound (DecisionReceiveRound, NO_MAJORITY): DecisionReceiveRound @@ -74,11 +85,17 @@ transition_func: (RedeemRound, NO_MAJORITY): RedeemRound (RedeemRound, NO_REDEEMING): FinishedWithoutRedeemingRound (RedeemRound, REDEEM_ROUND_TIMEOUT): FinishedWithoutRedeemingRound - (SamplingRound, DONE): RandomnessRound + (SamplingRound, DONE): SubscriptionRound (SamplingRound, FETCH_ERROR): ImpossibleRound (SamplingRound, NONE): FinishedWithoutDecisionRound (SamplingRound, NO_MAJORITY): SamplingRound (SamplingRound, ROUND_TIMEOUT): SamplingRound + (SubscriptionRound, DONE): FinishedSubscriptionRound + (SubscriptionRound, NONE): SubscriptionRound + (SubscriptionRound, NO_MAJORITY): SubscriptionRound + (SubscriptionRound, NO_SUBSCRIPTION): RandomnessRound + (SubscriptionRound, ROUND_TIMEOUT): SubscriptionRound + (SubscriptionRound, SUBSCRIPTION_ERROR): SubscriptionRound (ToolSelectionRound, DONE): DecisionRequestRound (ToolSelectionRound, NONE): ToolSelectionRound (ToolSelectionRound, NO_MAJORITY): ToolSelectionRound diff --git a/packages/valory/skills/decision_maker_abci/models.py b/packages/valory/skills/decision_maker_abci/models.py index fb841ff83..8d72d6152 100644 --- a/packages/valory/skills/decision_maker_abci/models.py +++ b/packages/valory/skills/decision_maker_abci/models.py @@ -312,6 +312,13 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: kwargs, bool, ) + self.use_nevermined = self._ensure("use_nevermined", kwargs, bool) + self.mech_to_subscription_params: Dict[ + str, Any + ] = nested_list_todict_workaround( + kwargs, + "mech_to_subscription_params", + ) super().__init__(*args, **kwargs) @property diff --git a/packages/valory/skills/decision_maker_abci/payloads.py b/packages/valory/skills/decision_maker_abci/payloads.py index 24a0d1dbe..3790df766 100644 --- a/packages/valory/skills/decision_maker_abci/payloads.py +++ b/packages/valory/skills/decision_maker_abci/payloads.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # -# Copyright 2023 Valory AG +# Copyright 2023-2024 Valory AG # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -68,6 +68,20 @@ class RequestPayload(MultisigTxPayload): price: Optional[int] = None +@dataclass(frozen=True) +class SubscriptionPayload(MultisigTxPayload): + """Represents a transaction payload for subscribing.""" + + agreement_id: str = "" + + +@dataclass(frozen=True) +class ClaimPayload(BaseTxPayload): + """Represents a transaction payload for claiming a subscription.""" + + vote: bool + + @dataclass(frozen=True) class VotingPayload(BaseTxPayload): """Represents a transaction payload for voting.""" diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py index e4befd61b..6b4df9e4e 100644 --- a/packages/valory/skills/decision_maker_abci/rounds.py +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -37,6 +37,9 @@ from packages.valory.skills.decision_maker_abci.states.blacklisting import ( BlacklistingRound, ) +from packages.valory.skills.decision_maker_abci.states.claim_subscription import ( + ClaimRound, +) from packages.valory.skills.decision_maker_abci.states.decision_receive import ( DecisionReceiveRound, ) @@ -45,6 +48,7 @@ ) from packages.valory.skills.decision_maker_abci.states.final_states import ( FinishedDecisionMakerRound, + FinishedSubscriptionRound, FinishedWithoutDecisionRound, FinishedWithoutRedeemingRound, ImpossibleRound, @@ -53,6 +57,9 @@ from packages.valory.skills.decision_maker_abci.states.handle_failed_tx import ( HandleFailedTxRound, ) +from packages.valory.skills.decision_maker_abci.states.order_subscription import ( + SubscriptionRound, +) from packages.valory.skills.decision_maker_abci.states.randomness import RandomnessRound from packages.valory.skills.decision_maker_abci.states.redeem import RedeemRound from packages.valory.skills.decision_maker_abci.states.sampling import SamplingRound @@ -69,66 +76,79 @@ class DecisionMakerAbciApp(AbciApp[Event]): Initial round: SamplingRound - Initial states: {DecisionReceiveRound, HandleFailedTxRound, RedeemRound, SamplingRound} + Initial states: {ClaimRound, DecisionReceiveRound, HandleFailedTxRound, RedeemRound, SamplingRound} Transition states: 0. SamplingRound - done: 1. - - none: 10. + - none: 12. - no majority: 0. - round timeout: 0. - - fetch error: 13. - 1. RandomnessRound - - done: 2. - - round timeout: 1. + - fetch error: 16. + 1. SubscriptionRound + - done: 14. + - no subscription: 3. + - none: 1. + - subscription error: 1. - no majority: 1. - 2. ToolSelectionRound + - round timeout: 1. + 2. ClaimRound - done: 3. - - none: 2. + - subscription error: 2. - no majority: 2. - round timeout: 2. - 3. DecisionRequestRound - - done: 9. - - slots unsupported error: 5. - - no majority: 3. + 3. RandomnessRound + - done: 4. - round timeout: 3. - - none: 13. - 4. DecisionReceiveRound - - done: 6. - - mech response error: 5. + - no majority: 3. + 4. ToolSelectionRound + - done: 5. + - none: 4. - no majority: 4. - - tie: 5. - - unprofitable: 5. - round timeout: 4. - 5. BlacklistingRound - - done: 10. - - none: 13. + 5. DecisionRequestRound + - done: 11. + - slots unsupported error: 7. - no majority: 5. - round timeout: 5. - - fetch error: 13. - 6. BetPlacementRound - - done: 9. - - insufficient balance: 12. + - none: 16. + 6. DecisionReceiveRound + - done: 8. + - mech response error: 7. - no majority: 6. + - tie: 7. + - unprofitable: 7. - round timeout: 6. - - none: 13. - 7. RedeemRound - - done: 9. - - no redeeming: 11. + 7. BlacklistingRound + - done: 12. + - none: 16. - no majority: 7. - - redeem round timeout: 11. - - none: 13. - 8. HandleFailedTxRound - - blacklist: 5. - - no op: 7. + - round timeout: 7. + - fetch error: 16. + 8. BetPlacementRound + - done: 11. + - insufficient balance: 15. - no majority: 8. - 9. FinishedDecisionMakerRound - 10. FinishedWithoutDecisionRound - 11. FinishedWithoutRedeemingRound - 12. RefillRequiredRound - 13. ImpossibleRound + - round timeout: 8. + - none: 16. + 9. RedeemRound + - done: 11. + - no redeeming: 13. + - no majority: 9. + - redeem round timeout: 13. + - none: 16. + 10. HandleFailedTxRound + - blacklist: 7. + - no op: 9. + - no majority: 10. + 11. FinishedDecisionMakerRound + 12. FinishedWithoutDecisionRound + 13. FinishedWithoutRedeemingRound + 14. FinishedSubscriptionRound + 15. RefillRequiredRound + 16. ImpossibleRound - Final states: {FinishedDecisionMakerRound, FinishedWithoutDecisionRound, FinishedWithoutRedeemingRound, ImpossibleRound, RefillRequiredRound} + Final states: {FinishedDecisionMakerRound, FinishedSubscriptionRound, FinishedWithoutDecisionRound, FinishedWithoutRedeemingRound, ImpossibleRound, RefillRequiredRound} Timeouts: round timeout: 30.0 @@ -141,16 +161,31 @@ class DecisionMakerAbciApp(AbciApp[Event]): HandleFailedTxRound, DecisionReceiveRound, RedeemRound, + ClaimRound, } transition_function: AbciAppTransitionFunction = { SamplingRound: { - Event.DONE: RandomnessRound, + Event.DONE: SubscriptionRound, Event.NONE: FinishedWithoutDecisionRound, Event.NO_MAJORITY: SamplingRound, Event.ROUND_TIMEOUT: SamplingRound, # this is here because of `autonomy analyse fsm-specs` falsely reporting it as missing from the transition MarketManagerEvent.FETCH_ERROR: ImpossibleRound, }, + SubscriptionRound: { + Event.DONE: FinishedSubscriptionRound, + Event.NO_SUBSCRIPTION: RandomnessRound, + Event.NONE: SubscriptionRound, + Event.SUBSCRIPTION_ERROR: SubscriptionRound, + Event.NO_MAJORITY: SubscriptionRound, + Event.ROUND_TIMEOUT: SubscriptionRound, + }, + ClaimRound: { + Event.DONE: RandomnessRound, + Event.SUBSCRIPTION_ERROR: ClaimRound, + Event.NO_MAJORITY: ClaimRound, + Event.ROUND_TIMEOUT: ClaimRound, + }, RandomnessRound: { Event.DONE: ToolSelectionRound, Event.ROUND_TIMEOUT: RandomnessRound, @@ -212,6 +247,7 @@ class DecisionMakerAbciApp(AbciApp[Event]): FinishedDecisionMakerRound: {}, FinishedWithoutDecisionRound: {}, FinishedWithoutRedeemingRound: {}, + FinishedSubscriptionRound: {}, RefillRequiredRound: {}, ImpossibleRound: {}, } @@ -227,6 +263,7 @@ class DecisionMakerAbciApp(AbciApp[Event]): ) final_states: Set[AppState] = { FinishedDecisionMakerRound, + FinishedSubscriptionRound, FinishedWithoutDecisionRound, FinishedWithoutRedeemingRound, RefillRequiredRound, @@ -238,6 +275,7 @@ class DecisionMakerAbciApp(AbciApp[Event]): } db_pre_conditions: Dict[AppState, Set[str]] = { RedeemRound: set(), + ClaimRound: set(), DecisionReceiveRound: { get_name(SynchronizedData.final_tx_hash), }, @@ -252,6 +290,10 @@ class DecisionMakerAbciApp(AbciApp[Event]): get_name(SynchronizedData.tx_submitter), get_name(SynchronizedData.most_voted_tx_hash), }, + FinishedSubscriptionRound: { + get_name(SynchronizedData.tx_submitter), + get_name(SynchronizedData.most_voted_tx_hash), + }, FinishedWithoutDecisionRound: {get_name(SynchronizedData.sampled_bet_index)}, FinishedWithoutRedeemingRound: set(), RefillRequiredRound: set(), diff --git a/packages/valory/skills/decision_maker_abci/skill.yaml b/packages/valory/skills/decision_maker_abci/skill.yaml index 4d858440d..ad45d1848 100644 --- a/packages/valory/skills/decision_maker_abci/skill.yaml +++ b/packages/valory/skills/decision_maker_abci/skill.yaml @@ -12,35 +12,39 @@ fingerprint: README.md: bafybeia367zzdwndvlhw27rvnwodytjo3ms7gbc3q7mhrrjqjgfasnk47i __init__.py: bafybeih563ujnigeci2ldzh7hakbau6a222vsed7leg3b7lq32vcn3nm4a behaviours/__init__.py: bafybeih6ddz2ocvm6x6ytvlbcz6oi4snb5ee5xh5h65nq4w2qf7fd7zfky - behaviours/base.py: bafybeifi3r27updo7qtqlqophabgr3v5sk7mu6i3pvsgxl3xmsbq7zwmli + behaviours/base.py: bafybeid5di6dt45myls6nggxd5422vlkqzgxb4hxe4lmdolgvmnntzemlm behaviours/bet_placement.py: bafybeib7jgq7iyfoyj3ur3xkj6knh7t7vr32kc743ztyjskkdevyh6l75q behaviours/blacklisting.py: bafybeic7o2jzmhbamdcebbdjeie3dkm7y2wq7xutpeshqmrt2hdq65vlai + behaviours/claim_subscription.py: bafybeihctnttpg5ckv5653iqwidbfxqif3ox35zwuaf52wfjfmpgtxv7em behaviours/decision_receive.py: bafybeig4gmlad7epkaao6nsfqya3zqhllt4hwiijtarcczmokxekxehrcu behaviours/decision_request.py: bafybeicpvpsdbcfvtiapnprp4j45exy5atyshfwgzksj3jptstlv6kgiwy behaviours/handle_failed_tx.py: bafybeidxpc6u575ymct5tdwutvzov6zqfdoio5irgldn3fw7q3lg36mmxm + behaviours/order_subscription.py: bafybeiehmurti6cmarlaqqgr45fou6js5gb4cbbul7n36chyb4ks3jxtay behaviours/randomness.py: bafybeidmr33teizrs4uxlo5tdz766ds6os4pe5lttstm7jpmhgmjz5ti3q behaviours/reedem.py: bafybeibenzbek5qdtk3gobjxfmsm6tg6i3d4yltozymy23izpgbcpal5ye - behaviours/round_behaviour.py: bafybeibymtirxbazjalmvfstnohaeqhmk4tggwwqhu62gm7mxdv6eabe3y + behaviours/round_behaviour.py: bafybeidrks62unrnoyp3jnbz2nozgnittfntyknymuuja7jwcsjuap4fve behaviours/sampling.py: bafybeibtkli72qsvotkrsepkgpiumtr5sershtkpb427oygnszs3dpgxry behaviours/tool_selection.py: bafybeicxw4je76uc7znx4u2hq2b2aaxcf7blwfla7lhdhkqnf3kkupsczq dialogues.py: bafybeigpwuzku3we7axmxeamg7vn656maww6emuztau5pg3ebsoquyfdqm - fsm_specification.yaml: bafybeigub5ekfxfj4xaitern7lbjtlpdgjqzgwmuijrea5b6dpu2npau7m + fsm_specification.yaml: bafybeigxmyqggc6m5jmmnw5cugfcr7irugjrn6jffgc3q2snevuvcan7zu handlers.py: bafybeiggoetspwcvdojmbjdd67tmkoeedikmt6vsbcium3zjaljb6jzqu4 io_/__init__.py: bafybeifxgmmwjqzezzn3e6keh2bfo4cyo7y5dq2ept3stfmgglbrzfl5rq io_/loader.py: bafybeih3sdsx5dhe4kzhtoafexjgkutsujwqy3zcdrlrkhtdks45bc7exa - models.py: bafybeigpbysmscqdweukb7xbl5uoyjc6e6h2sbkf4zmreyg7qi5prnpa6m - payloads.py: bafybeigcic4vewdglakzpyqevhfwsolh4ywnbvxo5bgned7gl5uo3jif7m + models.py: bafybeihzxf3dhx73lk7d56ir7zhmz2x6u5x6aga6f7rfms37zkvaqhfelm + payloads.py: bafybeibmjrckgsflqulviynadl6bx3keuabvhfolkiib7cmjhfsov2vuty policy.py: bafybeihca4gc5gdj3wmvtzulqq3cr4zm6ouyt7aoscfedutzr4so4bksna redeem_info.py: bafybeifiiix4gihfo4avraxt34sfw35v6dqq45do2drrssei2shbps63mm - rounds.py: bafybeifdqkwosejktea7az6fzzs3gtxkkxcq2kcmgdy4zntgdy42x5sn7q + rounds.py: bafybeih6bo7nnc7hvg67svio3f6cwob55qp4bgxjcejsmj6el3zhltz7bq states/__init__.py: bafybeid23llnyp6j257dluxmrnztugo5llsrog7kua53hllyktz4dqhqoy - states/base.py: bafybeiag6q3nlndsu4nt66rxrgogueh364v3lekqo4c5ynuuo4hql2bxum + states/base.py: bafybeid4khumtvfw5n37avkf3kujfxdwhfjcsnzpmyjeljkpcako5f2duy states/bet_placement.py: bafybeibalhxhp2c4oljmiwqi6ds3g36fgtabmf42mb5sgq6z22znrcbhda states/blacklisting.py: bafybeifruvxwwltndwazkzzbpc5nnnad3z5t5ofy6d3i7nssljbxl6gvxu + states/claim_subscription.py: bafybeiampifhdoztggwj6gthl2hfzecmjcwnm6nic2o47q4je7j4x3ujne states/decision_receive.py: bafybeib3roo27nccj4eylaqwwzztzzzwsi6px5atjrch5wbqmpln2kyy6y states/decision_request.py: bafybeic7otc3hjb753svbmur3yyk6szahc25yii3x4w4vcnpfz6jwvacuu - states/final_states.py: bafybeidiwhuyd5zm2cq7vhv2owcrxdpm7fnvn3db6p6tql4jz5hgpalflu + states/final_states.py: bafybeidmlhpjvsdogejrvaczk3tfcbh7f7ijectshh23qi5yzktozzunee states/handle_failed_tx.py: bafybeihewm2vernvhktuorljdupjqcg2p5vs6wvsira2d62wkoyo5xlzjm + states/order_subscription.py: bafybeiaoemltuh3zkch7hvaxzxxsofe3pomlgifj5hamkqqon5mg5eaoqq states/randomness.py: bafybeifgsyipvvu2e6caggyoo5vsmd64uexuho5ybyf3ry424r7kldcfre states/redeem.py: bafybeiblidmpt73ocac73wmakusrnf5267vs7s2foc5pqkrz4vuqgy3b4u states/sampling.py: bafybeihriyyskmlupjiwytx2pdpftms7plkjku5bip64c3ztx7oi3n43ci @@ -49,6 +53,8 @@ fingerprint: tests/behaviours/__init__.py: bafybeic7icz7lfhfepdkqkase7y7zn3a6pwdw6fx4ah2hajmgejawpolc4 tests/behaviours/test_base.py: bafybeidemmwjhtr6i4tq66jyqkpmb425ersfmk7k7zzjmeaz2idqnpcwve tests/conftest.py: bafybeidy5hw56kw5mxudnfbhvogofn6k4rqb4ux2bd45baedrrhmgyrude + utils/__init__.py: bafybeiazrfg3kwfdl5q45azwz6b6mobqxngxpf4hazmrnkhinpk4qhbbf4 + utils/nevermined.py: bafybeidw4ydwqo7dmdfu2oqx4lmgho5ciakthx6rj2iwgt7bywvnppbxuy fingerprint_ignore_patterns: [] connections: [] contracts: @@ -61,6 +67,7 @@ contracts: - valory/realitio:0.1.0:bafybeic5ie4oodetj4krdogydvbfxg4qggc3matpiflocah626tpevpreq - valory/realitio_proxy:0.1.0:bafybeidx37xzjjmapwacedgzhum6grfzhp5vhouz4zu3pvpgdy5pgb2fr4 - valory/agent_registry:0.1.0:bafybeifdsvdensn52cngcwdcathmlu6vhsmzcljemfrb5uozsigzgwcvuy +- valory/transfer_nft_condition:0.1.0:bafybeig6j2tgy54mjpeqntrrcdzi32cogche57k4spsz6nz3yvouvb275u protocols: - valory/contract_api:1.0.0:bafybeidgu7o5llh26xp3u3ebq3yluull5lupiyeu6iooi2xyymdrgnzq5i - valory/ledger_api:1.0.0:bafybeihdk6psr4guxmbcrc26jr2cbgzpd5aljkqvpwo64bvaz7tdti2oni @@ -194,6 +201,28 @@ models: slippage: 0.01 policy_epsilon: 0.1 use_subgraph_for_redeeming: true + use_nevermined: true + mech_to_subscription_params: + - - base_url + - https://marketplace-api.gnosis.nevermined.app/api/v1/metadata/assets/ddo + - - did + - did:nv:416e35cb209ecbfbf23e1192557b06e94c5d9a9afb025cce2e9baff23e907195 + - - escrow_payment_condition_address + - '0x9dDC4F1Ea5b94C138A23b60EC48c0d01d172629a' + - - lock_payment_condition_address + - '0xDE85A368Ee6f374d236500d176814365370778dA' + - - transfer_nft_condition_address + - '0xbBa4A25262745a55f020D0a3E9a82c25bb6F4979' + - - token_address + - '0xa30DE8C6aC39B825192e5F1FADe0770332D279A8' + - - order_address + - '0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4' + - - payment_token + - '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83' + - - nft_amount + - '1' + - - price + - '0' irrelevant_tools: - openai-text-davinci-002 - openai-text-davinci-003 @@ -309,6 +338,8 @@ dependencies: version: ==0.2.1 web3: version: <7,>=6.0.0 + eth-abi: + version: ==4.0.0 pyyaml: version: <=6.0.1,>=3.10 is_abstract: true diff --git a/packages/valory/skills/decision_maker_abci/states/base.py b/packages/valory/skills/decision_maker_abci/states/base.py index d4b6eb8a8..95c9a0638 100644 --- a/packages/valory/skills/decision_maker_abci/states/base.py +++ b/packages/valory/skills/decision_maker_abci/states/base.py @@ -51,6 +51,8 @@ class Event(Enum): NO_REDEEMING = "no_redeeming" BLACKLIST = "blacklist" NO_OP = "no_op" + SUBSCRIPTION_ERROR = "subscription_error" + NO_SUBSCRIPTION = "no_subscription" ROUND_TIMEOUT = "round_timeout" REDEEM_ROUND_TIMEOUT = "redeem_round_timeout" NO_MAJORITY = "no_majority" @@ -161,6 +163,16 @@ def participant_to_tx_prep(self) -> DeserializedCollection: """Get the participants to bet-placement.""" return self._get_deserialized("participant_to_tx_prep") + @property + def agreement_id(self) -> str: + """Get the agreement id.""" + return str(self.db.get_strict("agreement_id")) + + @property + def claim(self) -> bool: + """Get the claim.""" + return bool(self.db.get_strict("claim")) + class TxPreparationRound(CollectSameUntilThresholdRound): """A round for preparing a transaction.""" diff --git a/packages/valory/skills/decision_maker_abci/states/claim_subscription.py b/packages/valory/skills/decision_maker_abci/states/claim_subscription.py new file mode 100644 index 000000000..53b41baf2 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/states/claim_subscription.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023-2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the decision receiving state of the decision-making abci app.""" + +from packages.valory.skills.abstract_round_abci.base import VotingRound, get_name +from packages.valory.skills.decision_maker_abci.payloads import ClaimPayload +from packages.valory.skills.decision_maker_abci.states.base import ( + Event, + SynchronizedData, +) + + +class ClaimRound(VotingRound): + """A round for preparing a transaction.""" + + payload_class = ClaimPayload + synchronized_data_class = SynchronizedData + done_event = Event.DONE + negative_event = Event.SUBSCRIPTION_ERROR + no_majority_event = Event.NO_MAJORITY + collection_key = get_name(SynchronizedData.participant_to_votes) diff --git a/packages/valory/skills/decision_maker_abci/states/final_states.py b/packages/valory/skills/decision_maker_abci/states/final_states.py index e5c57043d..4339bc69d 100644 --- a/packages/valory/skills/decision_maker_abci/states/final_states.py +++ b/packages/valory/skills/decision_maker_abci/states/final_states.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # -# Copyright 2023 Valory AG +# Copyright 2023-2024 Valory AG # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -26,6 +26,10 @@ class FinishedDecisionMakerRound(DegenerateRound): """A round representing that decision-making has finished.""" +class FinishedSubscriptionRound(DegenerateRound): + """A round representing that subscription has finished.""" + + class FinishedWithoutRedeemingRound(DegenerateRound): """A round representing that decision-making has finished without redeeming.""" diff --git a/packages/valory/skills/decision_maker_abci/states/order_subscription.py b/packages/valory/skills/decision_maker_abci/states/order_subscription.py new file mode 100644 index 000000000..975b61e33 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/states/order_subscription.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023-2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the decision receiving state of the decision-making abci app.""" + +from enum import Enum +from typing import Optional, Tuple, Type + +from packages.valory.skills.abstract_round_abci.base import ( + BaseSynchronizedData, + get_name, +) +from packages.valory.skills.decision_maker_abci.payloads import ( + MultisigTxPayload, + SubscriptionPayload, +) +from packages.valory.skills.decision_maker_abci.states.base import ( + Event, + SynchronizedData, + TxPreparationRound, +) + + +class SubscriptionRound(TxPreparationRound): + """A round in which the agents prepare a tx to initiate a request to a mech to determine the answer to a bet.""" + + payload_class: Type[MultisigTxPayload] = SubscriptionPayload + selection_key = TxPreparationRound.selection_key + ( + get_name(SynchronizedData.agreement_id), + ) + none_event = Event.NO_SUBSCRIPTION + + NO_TX_PAYLOAD = "no_tx" + ERROR_PAYLOAD = "error" + + def end_block(self) -> Optional[Tuple[BaseSynchronizedData, Enum]]: + """Process the end of the block.""" + if self.threshold_reached: + tx_hash = self.most_voted_payload_values[1] + if tx_hash == self.ERROR_PAYLOAD: + return self.synchronized_data, Event.SUBSCRIPTION_ERROR + + if tx_hash == self.NO_TX_PAYLOAD: + return self.synchronized_data, Event.NO_SUBSCRIPTION + + update = super().end_block() + if update is None: + return None + + sync_data, event = update + agreement_id = self.most_voted_payload_values[2] + sync_data = sync_data.update( + agreement_id=agreement_id, + ) + return sync_data, event diff --git a/packages/valory/skills/decision_maker_abci/utils/__init__.py b/packages/valory/skills/decision_maker_abci/utils/__init__.py new file mode 100644 index 000000000..fc72e841a --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/utils/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This package contains helpers.""" diff --git a/packages/valory/skills/decision_maker_abci/utils/nevermined.py b/packages/valory/skills/decision_maker_abci/utils/nevermined.py new file mode 100644 index 000000000..aafe719fd --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/utils/nevermined.py @@ -0,0 +1,373 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the behaviour for the decision-making of the skill.""" + +import re +import uuid +from typing import Any, Dict, List, Tuple + +from eth_abi import encode +from web3 import Web3 + + +def zero_x_transformer(input_str: str, zero_output: bool = True) -> str: + """Transform a string to a hex string.""" + match = re.match(r"^(?:0x)*([a-f0-9]+)$", input_str, re.IGNORECASE) + valid = match is not None + output = match.group(1) if valid else "" # type: ignore + + return ("0x" if zero_output and valid else "") + output + + +def generate_id(length: int = 64) -> str: + """Generate a random ID.""" + generated_id = "" + while len(generated_id) < length: + generated_id += str(uuid.uuid4()).replace("-", "") + + return generated_id[:length] + + +def find_service_by_type(did_doc: Dict[str, Any], type: str) -> Dict[str, Any]: + """Find a service by name.""" + services = did_doc.get("service", []) + for service in services: + if service.get("type") == type: + return service + + raise Exception(f"No service found with type {type}") + + +def find_service_condition_by_name( + service: Dict[str, Any], name: str +) -> Dict[str, Any]: + """Find a condition by name.""" + conditions = ( + service.get("attributes", {}) + .get("serviceAgreementTemplate", {}) + .get("conditions", []) + ) + + condition = next((c for c in conditions if c["name"] == name), None) + + if condition is None: + raise Exception(f"Condition '{name!r}' not found.") + + return condition + + +def get_asset_price_from_service(service: Dict[str, Any]) -> Dict[str, int]: + """Get the price of a DID.""" + escrow_payment_condition = find_service_condition_by_name(service, "escrowPayment") + + if not escrow_payment_condition: + raise Exception("escrowPayment not found in service") + + amounts: List = next( + ( + p["value"] + for p in escrow_payment_condition.get("parameters", []) + if p["name"] == "_amounts" + ), + [], + ) + receivers: List = next( + ( + p["value"] + for p in escrow_payment_condition.get("parameters", []) + if p["name"] == "_receivers" + ), + [], + ) + + rewards_map = dict(zip(receivers, map(int, amounts))) + + return rewards_map + + +def get_price(did_doc: Dict[str, Any], type: str = "nft-sales") -> Dict[str, int]: + """Get the price of a DID.""" + service = find_service_by_type(did_doc, type) + return get_asset_price_from_service(service) + + +def get_nft_address(did_doc: Dict[str, Any], type: str = "nft-sales") -> str: + """Get the NFT address of a DID.""" + service = find_service_by_type(did_doc, type) + transfer_condition = find_service_condition_by_name(service, "transferNFT") + contract_param = next( + ( + p["value"] + for p in transfer_condition.get("parameters", []) + if p["name"] == "_contractAddress" or p["name"] == "_contract" + ), + None, + ) + + return contract_param if contract_param is not None else "" + + +def get_nft_holder(did_doc: Dict[str, Any], type: str = "nft-sales") -> str: + """Get the NFT holder of a DID.""" + service = find_service_by_type(did_doc, type) + transfer_condition = find_service_condition_by_name(service, "transferNFT") + contract_param = next( + ( + p["value"] + for p in transfer_condition.get("parameters", []) + if p["name"] == "_nftHolder" + ), + None, + ) + + return contract_param if contract_param is not None else "" + + +def get_nft_transfer(did_doc: Dict[str, Any], type: str = "nft-sales") -> str: + """Get the NFT holder of a DID.""" + service = find_service_by_type(did_doc, type) + transfer_condition = find_service_condition_by_name(service, "transferNFT") + contract_param = next( + ( + p["value"] + for p in transfer_condition.get("parameters", []) + if p["name"] == "_nftTransfer" + ), + None, + ) + + return contract_param if contract_param is not None else "" + + +def no_did_prefixed(input_string: str) -> str: + """Remove the DID prefix from a string.""" + return did_transformer(input_string, False) + + +def did_transformer(input_string: str, prefix_output: bool = False) -> str: + """Transform a string to a DID.""" + pattern = re.compile(r"^(?:0x|did:nv:)*([a-f0-9]{64})$", re.IGNORECASE) + match_result = input_match(input_string, pattern) + + valid, output = match_result["valid"], match_result["output"] + + return ("did:nv:" if prefix_output and valid else "") + output + + +def input_match(input_string: str, pattern: re.Pattern[str]) -> Dict[str, Any]: + """Match an input string with a pattern.""" + match_result = re.match(pattern, input_string) + if match_result: + return {"valid": True, "output": match_result.group(1)} + else: + return {"valid": False, "output": ""} + + +def hash_data( + types: List[str], + values: List[Any], +) -> str: + """Hash data.""" + encoded_data = encode(types, values) + return Web3.keccak(encoded_data).hex() + + +def short_id(did: str) -> str: + """Get the short ID of a DID.""" + return did.replace("did:nv:", "") + + +def get_agreement_id(seed: str, creator: str) -> str: + """Get the agreement ID.""" + seed_0x = zero_x_transformer(seed) + creator_0x = Web3.to_checksum_address(creator) + return hash_data(["bytes32", "address"], [bytes.fromhex(seed_0x[2:]), creator_0x]) + + +def get_lock_payment_seed( + agreement_id: str, + did_doc: Dict[str, Any], + lock_payment_condition_address: str, + escrow_payment_condition_address: str, + token_address: str, + amounts: List[int], + receivers: List[str], +) -> Tuple[str, str]: + """Get the lock payment seed.""" + short_id_ = zero_x_transformer(short_id(did_doc["id"])) + escrow_payment_condition_address_0x = zero_x_transformer( + escrow_payment_condition_address + ) + token_address = Web3.to_checksum_address(token_address) + receivers = [Web3.to_checksum_address(receiver) for receiver in receivers] + + hash_values = hash_data( + ["bytes32", "address", "address", "uint256[]", "address[]"], + [ + bytes.fromhex(short_id_[2:]), + escrow_payment_condition_address_0x, + token_address, + amounts, + receivers, + ], + ) + return hash_values, hash_data( + ["bytes32", "address", "bytes32"], + [ + bytes.fromhex(agreement_id[2:]), + lock_payment_condition_address, + bytes.fromhex(hash_values[2:]), + ], + ) + + +def get_transfer_nft_condition_seed( + agreement_id: str, + did_doc: Dict[str, Any], + buyer_address: str, + nft_amount: int, + transfer_nft_condition_address: str, + lock_condition_id: str, + nft_contract_address: str, + expiration: int = 0, +) -> Tuple[str, str]: + """Get the lock payment seed.""" + short_id_ = zero_x_transformer(short_id(did_doc["id"])) + nft_holder = Web3.to_checksum_address(get_nft_holder(did_doc)) + will_transfer = get_nft_transfer(did_doc) == "true" + + hash_values = hash_data( + ["bytes32", "address", "address", "uint256", "bytes32", "address", "bool"], + [ + bytes.fromhex(short_id_[2:]), + nft_holder, + Web3.to_checksum_address(buyer_address), + nft_amount, + bytes.fromhex(lock_condition_id[2:]), + Web3.to_checksum_address(nft_contract_address), + will_transfer, + ], + ) + + return hash_values, hash_data( + ["bytes32", "address", "bytes32"], + [ + bytes.fromhex(agreement_id[2:]), + transfer_nft_condition_address, + bytes.fromhex(hash_values[2:]), + ], + ) + + +def get_escrow_payment_seed( + agreement_id: str, + did_doc: Dict[str, Any], + amounts: List[int], + receivers: List[str], + buyer_address: str, + escrow_payment_condition_address: str, + token_address: str, + lock_seed: str, + access_seed: str, +) -> Tuple[str, str]: + """Get the escrow payment seed.""" + short_id_ = zero_x_transformer(short_id(did_doc["id"])) + escrow_payment_condition_address = Web3.to_checksum_address( + escrow_payment_condition_address + ) + receivers = [Web3.to_checksum_address(receiver) for receiver in receivers] + buyer_address = Web3.to_checksum_address(buyer_address) + token_address = Web3.to_checksum_address(token_address) + + values_hash = hash_data( + [ + "bytes32", + "uint256[]", + "address[]", + "address", + "address", + "address", + "bytes32", + "bytes32[]", + ], + [ + bytes.fromhex(short_id_[2:]), + amounts, + receivers, + buyer_address, + escrow_payment_condition_address, + token_address, + bytes.fromhex(lock_seed[2:]), + [bytes.fromhex(access_seed[2:])], + ], + ) + + return values_hash, hash_data( + ["bytes32", "address", "bytes32"], + [ + bytes.fromhex(agreement_id[2:]), + escrow_payment_condition_address, + bytes.fromhex(values_hash[2:]), + ], + ) + + +def get_timeouts_and_timelocks(did_doc: Dict[str, Any]) -> Tuple[List[int], List[int]]: + """Get timeouts and timelocks""" + type = "nft-sales" + service = find_service_by_type(did_doc, type) + conditions = ( + service.get("attributes", {}) + .get("serviceAgreementTemplate", {}) + .get("conditions", []) + ) + timeouts, timelocks = [], [] + for condition in conditions: + timeouts.append(condition.get("timeout", 0)) + timelocks.append(condition.get("timelock", 0)) + + return timeouts, timelocks + + +def get_reward_address(did_doc: Dict[str, Any], type: str = "nft-sales") -> str: + """Get the reward address of a DID.""" + service = find_service_by_type(did_doc, type) + transfer_condition = find_service_condition_by_name(service, "lockPayment") + contract_param = next( + ( + p["value"] + for p in transfer_condition.get("parameters", []) + if p["name"] == "_rewardAddress" + ), + None, + ) + + return contract_param if contract_param is not None else "" + + +def get_creator(did_doc: Dict[str, Any]) -> str: + """Get the creator of a DID.""" + return did_doc["proof"]["creator"] + + +def get_claim_endpoint(did_doc: Dict[str, Any]) -> str: + """Get the claim endpoint of a DID.""" + service = find_service_by_type(did_doc, "nft-sales") + return service["serviceEndpoint"] diff --git a/packages/valory/skills/trader_abci/composition.py b/packages/valory/skills/trader_abci/composition.py index 9a483e85c..1d4a1f10f 100644 --- a/packages/valory/skills/trader_abci/composition.py +++ b/packages/valory/skills/trader_abci/composition.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # -# Copyright 2023 Valory AG +# Copyright 2023-2024 Valory AG # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,11 +25,15 @@ ) from packages.valory.skills.abstract_round_abci.base import BackgroundAppConfig from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerAbciApp +from packages.valory.skills.decision_maker_abci.states.claim_subscription import ( + ClaimRound, +) from packages.valory.skills.decision_maker_abci.states.decision_receive import ( DecisionReceiveRound, ) from packages.valory.skills.decision_maker_abci.states.final_states import ( FinishedDecisionMakerRound, + FinishedSubscriptionRound, FinishedWithoutDecisionRound, FinishedWithoutRedeemingRound, RefillRequiredRound, @@ -80,6 +84,7 @@ FinishedDecisionRequestTxRound, FinishedRedeemingTxRound, FinishedStakingTxRound, + FinishedSubscriptionTxRound, PostTxSettlementRound, PreTxSettlementRound, TxSettlementMultiplexerAbciApp, @@ -94,8 +99,10 @@ ChecksPassedRound: RandomnessTransactionSubmissionRound, RefillRequiredRound: ResetAndPauseRound, FinishedTransactionSubmissionRound: PostTxSettlementRound, + FinishedSubscriptionTxRound: ClaimRound, FailedTransactionSubmissionRound: HandleFailedTxRound, FinishedDecisionRequestTxRound: DecisionReceiveRound, + FinishedSubscriptionRound: PreTxSettlementRound, FinishedBetPlacementTxRound: RedeemRound, FinishedRedeemingTxRound: CallCheckpointRound, FinishedWithoutDecisionRound: RedeemRound, diff --git a/packages/valory/skills/trader_abci/fsm_specification.yaml b/packages/valory/skills/trader_abci/fsm_specification.yaml index a51e77ad0..a9fb222d7 100644 --- a/packages/valory/skills/trader_abci/fsm_specification.yaml +++ b/packages/valory/skills/trader_abci/fsm_specification.yaml @@ -20,6 +20,7 @@ alphabet_in: - NO_MAJORITY - NO_OP - NO_REDEEMING +- NO_SUBSCRIPTION - REDEEMING_DONE - REDEEM_ROUND_TIMEOUT - REFILL_REQUIRED @@ -29,6 +30,8 @@ alphabet_in: - SERVICE_NOT_STAKED - SLOTS_UNSUPPORTED_ERROR - STAKING_DONE +- SUBSCRIPTION_DONE +- SUBSCRIPTION_ERROR - SUSPICIOUS_ACTIVITY - TIE - UNPROFITABLE @@ -48,6 +51,7 @@ states: - CallCheckpointRound - CheckLateTxHashesRound - CheckTransactionHistoryRound +- ClaimRound - CollectSignatureRound - DecisionReceiveRound - DecisionRequestRound @@ -68,6 +72,7 @@ states: - SelectKeeperTransactionSubmissionARound - SelectKeeperTransactionSubmissionBAfterTimeoutRound - SelectKeeperTransactionSubmissionBRound +- SubscriptionRound - SynchronizeLateMessagesRound - ToolSelectionRound - UpdateBetsRound @@ -100,6 +105,10 @@ transition_func: (CheckTransactionHistoryRound, NEGATIVE): SelectKeeperTransactionSubmissionBRound (CheckTransactionHistoryRound, NONE): HandleFailedTxRound (CheckTransactionHistoryRound, NO_MAJORITY): CheckTransactionHistoryRound + (ClaimRound, DONE): RandomnessRound + (ClaimRound, NO_MAJORITY): ClaimRound + (ClaimRound, ROUND_TIMEOUT): ClaimRound + (ClaimRound, SUBSCRIPTION_ERROR): ClaimRound (CollectSignatureRound, DONE): FinalizationRound (CollectSignatureRound, NO_MAJORITY): ResetRound (CollectSignatureRound, ROUND_TIMEOUT): CollectSignatureRound @@ -128,6 +137,7 @@ transition_func: (PostTxSettlementRound, REDEEMING_DONE): CallCheckpointRound (PostTxSettlementRound, ROUND_TIMEOUT): PostTxSettlementRound (PostTxSettlementRound, STAKING_DONE): ResetAndPauseRound + (PostTxSettlementRound, SUBSCRIPTION_DONE): ClaimRound (PostTxSettlementRound, UNRECOGNIZED): FailedMultiplexerRound (PreTxSettlementRound, CHECKS_PASSED): RandomnessTransactionSubmissionRound (PreTxSettlementRound, NO_MAJORITY): PreTxSettlementRound @@ -153,7 +163,7 @@ transition_func: (ResetRound, DONE): RandomnessTransactionSubmissionRound (ResetRound, NO_MAJORITY): HandleFailedTxRound (ResetRound, RESET_TIMEOUT): HandleFailedTxRound - (SamplingRound, DONE): RandomnessRound + (SamplingRound, DONE): SubscriptionRound (SamplingRound, FETCH_ERROR): ImpossibleRound (SamplingRound, NONE): RedeemRound (SamplingRound, NO_MAJORITY): SamplingRound @@ -172,6 +182,12 @@ transition_func: (SelectKeeperTransactionSubmissionBRound, INCORRECT_SERIALIZATION): HandleFailedTxRound (SelectKeeperTransactionSubmissionBRound, NO_MAJORITY): ResetRound (SelectKeeperTransactionSubmissionBRound, ROUND_TIMEOUT): SelectKeeperTransactionSubmissionBRound + (SubscriptionRound, DONE): PreTxSettlementRound + (SubscriptionRound, NONE): SubscriptionRound + (SubscriptionRound, NO_MAJORITY): SubscriptionRound + (SubscriptionRound, NO_SUBSCRIPTION): RandomnessRound + (SubscriptionRound, ROUND_TIMEOUT): SubscriptionRound + (SubscriptionRound, SUBSCRIPTION_ERROR): SubscriptionRound (SynchronizeLateMessagesRound, DONE): CheckLateTxHashesRound (SynchronizeLateMessagesRound, NONE): SelectKeeperTransactionSubmissionBRound (SynchronizeLateMessagesRound, ROUND_TIMEOUT): SynchronizeLateMessagesRound diff --git a/packages/valory/skills/trader_abci/skill.yaml b/packages/valory/skills/trader_abci/skill.yaml index 4db0b44bf..eb42c7478 100644 --- a/packages/valory/skills/trader_abci/skill.yaml +++ b/packages/valory/skills/trader_abci/skill.yaml @@ -9,9 +9,9 @@ fingerprint: README.md: bafybeiab4xgadptz4mhvno4p6xvkh7p4peg7iuhotabydriu74dmj6ljga __init__.py: bafybeido7wa33h4dtleap57vzgyb4fsofk4vindsqcekyfo5i56i2rll2a behaviours.py: bafybeieesrefrpo5c5upzztgm5fwfrmxeagilacuau5nacobhsfvgpbzby - composition.py: bafybeiajga2m7pv7v4bxsjsnncavdns6gujg7qg7opfjyznlzip3gbd3nm + composition.py: bafybeif75v3swf2pmxi3rdd5kqdfv4ap5uapcbszkiohhur67oemrgotay dialogues.py: bafybeiebofyykseqp3fmif36cqmmyf3k7d2zbocpl6t6wnlpv4szghrxbm - fsm_specification.yaml: bafybeifkeuotkaoeigtohopkm5mxgmsl2jdlsg2obpizqel6vjtyyz5uzm + fsm_specification.yaml: bafybeiclt5bzaw66fqjlt2edu5ia26k4vbuto3dxhtgzdr2crcc7hxotfi handlers.py: bafybeibkiqwe7hoqccjirimd44nzeqkabc7oo74romqklssion27s5sa2a models.py: bafybeibrxivgpzamfmrl6bdngi67g72i32lmu5vgdn3jbwka265blk7ire fingerprint_ignore_patterns: [] @@ -25,8 +25,8 @@ skills: - valory/transaction_settlement_abci:0.1.0:bafybeic3ysdc46z4ipuonc2g6vdyqaxxljvfd45cflzi2xq7o7hre6lvvy - valory/termination_abci:0.1.0:bafybeif7dwj4i5okp7rsyeiyvnmt5xop7njvj27bmjqdx4skmimqls7t4e - valory/market_manager_abci:0.1.0:bafybeif23nzty3mvhvx3tphgr3mdrfo4kadxzg4zi57at2pqvml5yrb2xa -- valory/decision_maker_abci:0.1.0:bafybeiax6d4sdiytgolepg24mm7fp7q2p3tpbiqrp7j7vaogrnfiw3girm -- valory/tx_settlement_multiplexer_abci:0.1.0:bafybeidwafoxlqclwxssstd5sgtlr2aypg5nylyfn4q7f4tplt2rjlq6c4 +- valory/decision_maker_abci:0.1.0:bafybeidh3efbssuohhuku5b4go5jeqti4xv6dsdpaxm3ti4rudgqmyujpq +- valory/tx_settlement_multiplexer_abci:0.1.0:bafybeiesf2zztqhie7rpsrglddc7lbjfjxtybqsqhlmtp77ukddejrmiva - valory/staking_abci:0.1.0:bafybeifflsc3m5pvxrbhih4xclslhpqdnryilp72wudqffvvttyiung76u behaviours: main: @@ -181,6 +181,30 @@ models: refill_check_interval: 10 redeem_round_timeout: 3600.0 tool_punishment_multiplier: 1 + use_nevermined: true + mech_to_subscription_params: + - - base_url + - https://marketplace-api.gnosis.nevermined.app/api/v1/metadata/assets/ddo + - - did + - did:nv:416e35cb209ecbfbf23e1192557b06e94c5d9a9afb025cce2e9baff23e907195 + - - escrow_payment_condition_address + - '0x9dDC4F1Ea5b94C138A23b60EC48c0d01d172629a' + - - lock_payment_condition_address + - '0xDE85A368Ee6f374d236500d176814365370778dA' + - - transfer_nft_condition_address + - '0xbBa4A25262745a55f020D0a3E9a82c25bb6F4979' + - - token_address + - '0xa30DE8C6aC39B825192e5F1FADe0770332D279A8' + - - order_address + - '0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4' + - - nft_amount + - '1' + - - payment_token + - '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83' + - - order_address + - '0xc7751eff5396a846e7bc83ac31d3cb7d37cb49e4' + - - price + - '0' contract_timeout: 300.0 file_hash_to_strategies_json: - - hash diff --git a/packages/valory/skills/tx_settlement_multiplexer_abci/fsm_specification.yaml b/packages/valory/skills/tx_settlement_multiplexer_abci/fsm_specification.yaml index 28be80054..5070c9adf 100644 --- a/packages/valory/skills/tx_settlement_multiplexer_abci/fsm_specification.yaml +++ b/packages/valory/skills/tx_settlement_multiplexer_abci/fsm_specification.yaml @@ -7,6 +7,7 @@ alphabet_in: - REFILL_REQUIRED - ROUND_TIMEOUT - STAKING_DONE +- SUBSCRIPTION_DONE - UNRECOGNIZED default_start_state: PreTxSettlementRound final_states: @@ -16,6 +17,7 @@ final_states: - FinishedDecisionRequestTxRound - FinishedRedeemingTxRound - FinishedStakingTxRound +- FinishedSubscriptionTxRound label: TxSettlementMultiplexerAbciApp start_states: - PostTxSettlementRound @@ -27,6 +29,7 @@ states: - FinishedDecisionRequestTxRound - FinishedRedeemingTxRound - FinishedStakingTxRound +- FinishedSubscriptionTxRound - PostTxSettlementRound - PreTxSettlementRound transition_func: @@ -35,6 +38,7 @@ transition_func: (PostTxSettlementRound, REDEEMING_DONE): FinishedRedeemingTxRound (PostTxSettlementRound, ROUND_TIMEOUT): PostTxSettlementRound (PostTxSettlementRound, STAKING_DONE): FinishedStakingTxRound + (PostTxSettlementRound, SUBSCRIPTION_DONE): FinishedSubscriptionTxRound (PostTxSettlementRound, UNRECOGNIZED): FailedMultiplexerRound (PreTxSettlementRound, CHECKS_PASSED): ChecksPassedRound (PreTxSettlementRound, NO_MAJORITY): PreTxSettlementRound diff --git a/packages/valory/skills/tx_settlement_multiplexer_abci/rounds.py b/packages/valory/skills/tx_settlement_multiplexer_abci/rounds.py index c97e4cc9c..bbb4f9795 100644 --- a/packages/valory/skills/tx_settlement_multiplexer_abci/rounds.py +++ b/packages/valory/skills/tx_settlement_multiplexer_abci/rounds.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # -# Copyright 2023 Valory AG +# Copyright 2023-2024 Valory AG # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -41,6 +41,9 @@ from packages.valory.skills.decision_maker_abci.states.decision_request import ( DecisionRequestRound, ) +from packages.valory.skills.decision_maker_abci.states.order_subscription import ( + SubscriptionRound, +) from packages.valory.skills.decision_maker_abci.states.redeem import RedeemRound from packages.valory.skills.staking_abci.rounds import CallCheckpointRound @@ -54,6 +57,7 @@ class Event(Enum): BET_PLACEMENT_DONE = "bet_placement_done" REDEEMING_DONE = "redeeming_done" STAKING_DONE = "staking_done" + SUBSCRIPTION_DONE = "subscription_done" ROUND_TIMEOUT = "round_timeout" UNRECOGNIZED = "unrecognized" NO_MAJORITY = "no_majority" @@ -92,6 +96,7 @@ def end_block(self) -> Optional[Tuple[BaseSynchronizedData, Enum]]: BetPlacementRound.auto_round_id(): Event.BET_PLACEMENT_DONE, RedeemRound.auto_round_id(): Event.REDEEMING_DONE, CallCheckpointRound.auto_round_id(): Event.STAKING_DONE, + SubscriptionRound.auto_round_id(): Event.SUBSCRIPTION_DONE, } synced_data = SynchronizedData(self.synchronized_data.db) @@ -127,6 +132,10 @@ class FinishedStakingTxRound(DegenerateRound): """Finished staking round.""" +class FinishedSubscriptionTxRound(DegenerateRound): + """Finished subscription round.""" + + class FailedMultiplexerRound(DegenerateRound): """Round that represents failure in identifying the transmitter round.""" @@ -147,18 +156,20 @@ class TxSettlementMultiplexerAbciApp(AbciApp[Event]): 1. PostTxSettlementRound - decision requesting done: 3. - bet placement done: 4. - - redeeming done: 5. - - staking done: 6. + - redeeming done: 6. + - staking done: 7. + - subscription done: 5. - round timeout: 1. - - unrecognized: 7. + - unrecognized: 8. 2. ChecksPassedRound 3. FinishedDecisionRequestTxRound 4. FinishedBetPlacementTxRound - 5. FinishedRedeemingTxRound - 6. FinishedStakingTxRound - 7. FailedMultiplexerRound + 5. FinishedSubscriptionTxRound + 6. FinishedRedeemingTxRound + 7. FinishedStakingTxRound + 8. FailedMultiplexerRound - Final states: {ChecksPassedRound, FailedMultiplexerRound, FinishedBetPlacementTxRound, FinishedDecisionRequestTxRound, FinishedRedeemingTxRound, FinishedStakingTxRound} + Final states: {ChecksPassedRound, FailedMultiplexerRound, FinishedBetPlacementTxRound, FinishedDecisionRequestTxRound, FinishedRedeemingTxRound, FinishedStakingTxRound, FinishedSubscriptionTxRound} Timeouts: round timeout: 30.0 @@ -178,12 +189,14 @@ class TxSettlementMultiplexerAbciApp(AbciApp[Event]): Event.BET_PLACEMENT_DONE: FinishedBetPlacementTxRound, Event.REDEEMING_DONE: FinishedRedeemingTxRound, Event.STAKING_DONE: FinishedStakingTxRound, + Event.SUBSCRIPTION_DONE: FinishedSubscriptionTxRound, Event.ROUND_TIMEOUT: PostTxSettlementRound, Event.UNRECOGNIZED: FailedMultiplexerRound, }, ChecksPassedRound: {}, FinishedDecisionRequestTxRound: {}, FinishedBetPlacementTxRound: {}, + FinishedSubscriptionTxRound: {}, FinishedRedeemingTxRound: {}, FinishedStakingTxRound: {}, FailedMultiplexerRound: {}, @@ -197,6 +210,7 @@ class TxSettlementMultiplexerAbciApp(AbciApp[Event]): FinishedBetPlacementTxRound, FinishedRedeemingTxRound, FinishedStakingTxRound, + FinishedSubscriptionTxRound, FailedMultiplexerRound, } db_pre_conditions: Dict[AppState, Set[str]] = { @@ -210,4 +224,5 @@ class TxSettlementMultiplexerAbciApp(AbciApp[Event]): FinishedRedeemingTxRound: set(), FinishedStakingTxRound: set(), FailedMultiplexerRound: set(), + FinishedSubscriptionTxRound: set(), } diff --git a/packages/valory/skills/tx_settlement_multiplexer_abci/skill.yaml b/packages/valory/skills/tx_settlement_multiplexer_abci/skill.yaml index cc36994c3..76655230a 100644 --- a/packages/valory/skills/tx_settlement_multiplexer_abci/skill.yaml +++ b/packages/valory/skills/tx_settlement_multiplexer_abci/skill.yaml @@ -10,10 +10,10 @@ fingerprint: __init__.py: bafybeide6k22zk4f3hyzhpapaoddsnxpw5elqcfvrxxj4nfvpzctv6jqhu behaviours.py: bafybeictumcqn2pgo7y2duemvzoaafognfhl6s6il3tv53hq66tf7xgpsu dialogues.py: bafybeiebofyykseqp3fmif36cqmmyf3k7d2zbocpl6t6wnlpv4szghrxbm - fsm_specification.yaml: bafybeidwxjna36auqbtqlehx73kyb6o74suhzj5bothgtiapcj35b7gzve + fsm_specification.yaml: bafybeib7nmznbbug6icxyahy34r6ory7ujkwnlakv3bgmnlecysr46l5v4 handlers.py: bafybeiafbqr7ojfcbwohvee7x4zzswad3ymfrrbjlfz7uuuttmn3qdfs6q models.py: bafybeigtmxoecoow663hgqnyinxarlrttyyt5ghpbdamdv4tc4kikcfx3a - rounds.py: bafybeictknoifjphmgviw3b6mh3srgqlw2lhfkedqu5fumoxuwpehlhioq + rounds.py: bafybeictmmkmkxsbij4zhcsth6avb477tzhqaycpjkx2igtec2czszx6hu fingerprint_ignore_patterns: [] connections: [] contracts: [] @@ -21,7 +21,7 @@ protocols: - valory/ledger_api:1.0.0:bafybeihdk6psr4guxmbcrc26jr2cbgzpd5aljkqvpwo64bvaz7tdti2oni skills: - valory/abstract_round_abci:0.1.0:bafybeibna634t4w4udainzsuxwfydkbcy33alcqy6ugalcfuhoyhr53gs4 -- valory/decision_maker_abci:0.1.0:bafybeiax6d4sdiytgolepg24mm7fp7q2p3tpbiqrp7j7vaogrnfiw3girm +- valory/decision_maker_abci:0.1.0:bafybeidh3efbssuohhuku5b4go5jeqti4xv6dsdpaxm3ti4rudgqmyujpq - valory/staking_abci:0.1.0:bafybeifflsc3m5pvxrbhih4xclslhpqdnryilp72wudqffvvttyiung76u behaviours: main: diff --git a/tox.ini b/tox.ini index 5d0a3b7b4..771e6e03a 100644 --- a/tox.ini +++ b/tox.ini @@ -314,6 +314,10 @@ ignore_errors=True [mypy-packages.valory.skills.abstract_round_abci.*] ignore_errors=True +[mypy-eth_abi.*] +ignore_errors=True +ignore_missing_imports= True + [mypy-packages.valory.skills.registration_abci.*] ignore_errors=True