diff --git a/app/api/__init__.py b/app/api/__init__.py index b356c99..bc6f464 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -13,7 +13,7 @@ app.register_middleware(close_db_session, "response") from app.api.routes._index import s_index from app.api.routes._system import s_api_system, s_api_system_version from app.api.routes.auth import s_api_v1_auth_twa -from app.api.routes.tonconnect import s_api_tonconnect_manifest +from app.api.routes.statics import s_api_tonconnect_manifest, s_api_platform_metadata from app.api.routes.node_storage import s_api_v1_storage_post, s_api_v1_storage_get from app.api.routes.account import s_api_v1_account_get from app.api.routes._blockchain import s_api_v1_blockchain_send_new_content_message, \ @@ -24,7 +24,8 @@ app.add_route(s_index, "/") app.add_route(s_api_system, "/api/node", methods=["GET", "OPTIONS"]) app.add_route(s_api_system_version, "/api/system.version", methods=["GET", "OPTIONS"]) -app.add_route(s_api_tonconnect_manifest, "/api/tonconnect-manifest.json") +app.add_route(s_api_tonconnect_manifest, "/api/tonconnect-manifest.json", methods=["GET", "OPTIONS"]) +app.add_route(s_api_platform_metadata, "/api/platform-metadata.json", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_auth_twa, "/api/v1/auth.twa", methods=["POST", "OPTIONS"]) diff --git a/app/api/routes/_system.py b/app/api/routes/_system.py index dc1c9bb..4ad2ebb 100644 --- a/app/api/routes/_system.py +++ b/app/api/routes/_system.py @@ -1,7 +1,10 @@ from sanic import response from base58 import b58encode from app.core._secrets import hot_pubkey, service_wallet +from app.core._blockchain.ton.platform import platform import subprocess +import docker +from pprint import pprint def get_git_info(): @@ -11,9 +14,15 @@ def get_git_info(): async def s_api_system(request): + client = docker.from_env() + containers = client.containers.list(all=True) + pprint(containers) + return response.json({ 'id': b58encode(hot_pubkey).decode(), - 'ton_address': service_wallet.address.to_string(1, 1, 1) + 'master_address': platform.address.to_string(1, 1, 1), + 'node_address': service_wallet.address.to_string(1, 1, 1), + 'indexer_height': 0, }) diff --git a/app/api/routes/tonconnect.py b/app/api/routes/statics.py similarity index 60% rename from app/api/routes/tonconnect.py rename to app/api/routes/statics.py index f43c6fa..dbb2065 100644 --- a/app/api/routes/tonconnect.py +++ b/app/api/routes/statics.py @@ -9,3 +9,10 @@ async def s_api_tonconnect_manifest(request): "name": f"{PROJECT_HOST}", # TODO: maybe edit "iconUrl": "https://github.com/projscale/assets/blob/main/ton-connect.png?raw=true", }) + + +async def s_api_platform_metadata(request): + return response.json({ + "name": "@MY", + "image": "https://git.projscale.dev/my-dev/assets/raw/commit/81de5356c6b1d6f01988d77e22c580114b32bcbd/images/logo.jpg" + }) diff --git a/app/core/_blockchain/ton/contracts/__init__.py b/app/core/_blockchain/ton/contracts/__init__.py new file mode 100644 index 0000000..139597f --- /dev/null +++ b/app/core/_blockchain/ton/contracts/__init__.py @@ -0,0 +1,2 @@ + + diff --git a/app/core/_blockchain/ton/contracts/blank.py b/app/core/_blockchain/ton/contracts/blank.py new file mode 100644 index 0000000..13bd930 --- /dev/null +++ b/app/core/_blockchain/ton/contracts/blank.py @@ -0,0 +1,22 @@ +from tonsdk.boc import Cell, begin_cell +from tonsdk.contract import Contract + +from app.core._secrets import service_wallet + + +class Blank(Contract): + code = 'B5EE9C72010104010042000114FF00F4A413F4BCF2C80B010202CA0203004FD043A0E9AE43F48061DA89A1F480618E0BE5C323A803A1A843F60803A1DA3DDAA7A861DAA9E2026F0007A0DD7C12' + + def __init__(self, **kwargs): + kwargs['code'] = Cell.one_from_boc(self.code) + super().__init__(**kwargs) + + def create_data_cell(self): + return begin_cell() \ + .store_address(service_wallet.address) \ + .store_uint(self.options['type_id'], 8) \ + .store_ref( + begin_cell() + .store_bytes(self.options['user_id']) + .end_cell() + ).end_cell() diff --git a/app/core/_blockchain/ton/contracts/cop_nft.py b/app/core/_blockchain/ton/contracts/cop_nft.py new file mode 100644 index 0000000..6597949 --- /dev/null +++ b/app/core/_blockchain/ton/contracts/cop_nft.py @@ -0,0 +1,50 @@ +from app.core._config import MY_PLATFORM_CONTRACT + +from tonsdk.contract import Contract +from tonsdk.utils import Address +from tonsdk.boc import Cell, begin_cell, begin_dict + + +class COP_NFT(Contract): + code = 'b5ee9c7241023701000d91000114ff00f4a413f4bcf2c80b0102012004020146f28308d71820f90101d33f01f823bbf264d31f0182102fa30f96bae3025f03840ff2f00301f2f8000282f06949f6f27c3c5650394e5f9b14a79bc4d647b8fe4870c0014ece6b05cb2bd747f910f2a3d31f01c203f2a5ed44d0d3ff01f862fa4001f86320d749c300f861f8418e1ad30701f864fa4001f865d401f866d401f867d401f868d401f869ded1f1015301d43020fb04d0ed1eed5370f861db3ced54120201480e050201200b060201200a0702016a0908004bae43b9c17834a4fb793e1e2b281ca72fcd8a53cde26b23dc7f24386000a7673582e595eba3c0007dac1ff6a26869ff80fc317d2000fc31906ba4e1807c30fc20c70d698380fc327d2000fc32ea00fc336a00fc33ea00fc346a00fc34ef68fc21fc217c227c22c000cbb8fcfed44d0d3ff01f862fa4001f86320d749c300f861f8418e1ad30701f864fa4001f865d401f866d401f867d401f868d401f869ded1f8419ac8c9707f22d023d05503e1f849d0f846f00702987ff84504d4305e21e05b7ff842f843f84504d4301034413080201200d0c00d5ba7a3ed44d0d3ff01f862fa4001f86320d749c300f861f8418e1ad30701f864fa4001f865d401f866d401f867d401f868d401f869ded1f848d0d431d430f8287003c8cbffc94130c85003cf16cb07ccc97020c8cb0113f400f400cb00c9f9007074c8cb02ca07cbffc9d080093bb54ded44d0d3ff01f862fa4001f86320d749c300f861f8418e1ad30701f864fa4001f865d401f866d401f867d401f868d401f869ded171f828f842f843f844f845f849f846f848f84780202c7200f0201481b1002027217110101f41201bac882f01bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6228307f40e6fa130d3ff3001cbff82f03dbe3c57aae062079b4712b8a650cf2abe2d6f986e0ecf1785d2ba835974175d228307f40e6fa130cf1613015e82f0e3868b37bc801236c714f134200a4e3211860f3ad67d22be995321452723c516228307f40e6fa1923031e30dc91401c0d3073001cb0782f0862bd78e42e143bb51660c521752437648ef67967d5055093d11be1117c774b0228307f40e6fa130cf1682f0ade34d680d2df2ad88267395ea1c3b159c4df766416d69d2bbcc8e18e87bd2ba228307f40e6fa130d43001cc1501fe82f093354845030274cd4bf1686abd60ab28ec52e1a792fa6f7ccb9cbd0ddff53d12228307f40e6fa130d43001cc82f0852d0f3fd8bdef2aab5f91714d86638ddc57db97743a350fab3394bc9ebd9518228307f40e6fa130d43001cc82f089445ea08b55421faa49919a5fd272e9a520f701b479d6084847e161ca5b7711581600168307f40e6fa130d43001cc01bbf36fc216465ffc1780de025a948e135236c8c09c89c5cc9696f4bb6b428e8449d823b5c2dfdefe3732c4183fa21e47c21e78b41781edf1e2bd5703103cda3895c532867955f16b7cc3707678bc2e95d41acba0baeac4183fa21fc20f18041801fef844c8cb0782f0e3868b37bc801236c714f134200a4e3211860f3ad67d22be995321452723c516588307f443c8f845cf1682f0862bd78e42e143bb51660c521752437648ef67967d5055093d11be1117c774b0588307f443f846c8cc82f0ade34d680d2df2ad88267395ea1c3b159c4df766416d69d2bbcc8e18e87bd2ba581901bc8307f443f847c8cc82f093354845030274cd4bf1686abd60ab28ec52e1a792fa6f7ccb9cbd0ddff53d12588307f443f848c8cc82f0852d0f3fd8bdef2aab5f91714d86638ddc57db97743a350fab3394bc9ebd9518588307f443f849c8cc1a004e82f089445ea08b55421faa49919a5fd272e9a520f701b479d6084847e161ca5b7711588307f4430202761d1c01cdf19683a6b90fd207d2018fd0018b8eb90fd0018fd00184008bd54c276a26869ff80fc317d2000fc31906ba4e1807c30fc20c70d698380fc327d2000fc32ea00fc336a00fc33ea00fc346a00fc34ef6889c0bc1ca9893781ff1018410802faf08000d0df7971044350201201f1e00415f849f848f847f846f844f842c8cbfff843cf16cb07f845cf16ccccccccc9ed54800694ed44d0d3ff01f862fa4001f86320d749c300f861f8418e1ad30701f864fa4001f865d401f866d401f867d401f868d401f869ded180201cb22210011f686900699ffd20184020148242300113e910c30003cb8536003f7007434c0c05c6c2497c1383e903e900c7e800c5c75c87e800c7e800c1cea6d003b513434ffc07e18be90007e18c835d270c03e187e106386b4c1c07e193e90007e1975007e19b5007e19f5007e1a35007e1a77b47e1063a64cd6a05e0e54c49bc0ff880c2084017d784000686fbcb88200785c14c0f5d2708838c02035342504b021c0008e1331337071c8cb07cbffc9d00382102a31959301de218210247a9ebabae3022182102a319593ba8e955b82102a319593f8444106256f04fe20304013db3ce0352082105fcc3d14bae30231342382102fcb26a2ba322c292602fe8e6c3132f846f007706d82108b771735c8cb1f16cb3f049702c8cbff01cf169b6c21f842c8cbfff843cf16e212cf17128040708010c8cb055006cf165004fa0214cb68216e947032cb019bc858cf17c97158cb00f400e2226e95327058cb0099c85003cf17c958f400e2c901fb00e0322282102fa30f96bae3023001810e7f28270094ba8e41f843c705f2e1917082f073ccd5a21784f5da1d259055c57d60234424f6b68276a282db629081f555000221748010c8cb05cb02cb07cbff21fa02cb6ac98306fb00e030840ff2f0002032f843c705f2e191d401fb04d430ed5402ec304330f84514c705f2e191f844c000f844c003b1f2e191fa4021f001fa40d20031fa0006821005f5e100a121945315a0a1de22d70b01c300209206a19136e220c2fff2e19223f865219410266c31e30d02925f03e30df849f848f847f846f844f842c8cbfff843cf16cb07f845cf16ccccccccc9ed542b2a009a22f0016d8210d53276dbc8cb1f12cb3f71708010c8cb055006cf165004fa0214cb68216e947032cb019bc858cf17c97158cb00f400e2226e95327058cb0099c85003cf17c958f400e2c901fb0000ae6d821005138d91c8cb1f5260cb3ff845cf165008cf1610341771708010c8cb055006cf165004fa0214cb68216e947032cb019bc858cf17c97158cb00f400e2226e95327058cb0099c85003cf17c958f400e2c901fb000301f6f847d0d403d30721c200f2e19a817919f8446f02fe2030f844c000f844c003b1f2e1a4f844c0039621c001f2e1aeded3ff317022c0039430fa00309131e204f4043021c0038e1022d0fa003024a70a01a05250bcf2e1c2de02d021a59320c2009601fa003101a5e830fa003023a705821005f5e100a05cbcf2e2082d01fe5371bef2e2085171a1f848d0f846f007258106a4a8812710a904536ca1b608812774fe2030705477d16f04fe2030f8436d8210247a9ebac8cb1f235970708010c8cb055006cf165004fa0214cb68216e947032cb019bc858cf17c97158cb00f400e2226e95327058cb0099c85003cf17c958f400e2c901fb00506ca1500ba12e02f88127dffe203053076f02fe20305420773121c2008ed88ed478f4966fa531208ec701d430d0fa40d30f30812a30fe20305340a8812710a90452106f02fe20305240a8812710a9046d70c8cb1f8d05935e535d5cda58c8149bde585b1d1e4814185e5bdd5d20cf16709131e2b3e65b915be2d4d430f828702bc8cbffc9332f01fec85003cf16cb07ccc97020c8cb0113f400f400cb00c920f9007074c8cb02ca07cbffc9d0f849d081283cfe2030821005f5e10029a703a0fe2030821005f5e10009a70a19a0c803d013cf16f849f8486dc8500dfa0270fa0270fa02c9c8cc1cf400c90ad3ff302dc8cbfff843cf16c906c8cc16ccc9c8cc15cbff2bcf16f8283001f8cf1617cb0718cc18cccc5e321570708010c8cb055006cf165004fa0214cb68216e947032cb019bc858cf17c97158cb00f400e2226e95327058cb0099c85003cf17c958f400e2c901fb0004a404c8ca0014cb3f01cf16c9f866f849f848f847f846f844f842c8cbfff843cf16cb07f845cf16ccccccccc9ed5421c2003100b68e568129ccfe203021fe20306d708210d53276dbc8cb1fcb3f102372708010c8cb055006cf165004fa0214cb68216e947032cb019bc858cf17c97158cb00f400e2226e95327058cb0099c85003cf17c958f400e2c901fb00915be201de10245f0432f847d0d431f40430123121c2008ed88ed478f4966fa531208ec701d430d0fa40d30f30812a30fe20305340a8812710a90452106f02fe20305240a8812710a9046d70c8cb1f8d05935e535d5cda58c8149bde585b1d1e4814185e5bdd5d20cf16709131e2b3e65b915be2330078708010c8cb055006cf165004fa0214cb68216e947032cb019bc858cf17c97158cb00f400e2226e95327058cb0099c85003cf17c958f400e2c901fb0000105b02d31fd33f444401ded3fffa4001f865f8435230c7058e5e31fa405312c7058e4c6c12d30701f864f84271c8cb00cb3f58cf16c9f866d401f867d401f868d430f86981783a71f8446f03fe2030f849f848f847f846f844f842c8cbfff843cf16cb07f845cf16ccccccccc9ed54e05bf843c705f2e1afe30d3600b43270f86470c8cb42c9f86601d401f867d401f868d430d020c701f2e19b20d74ac002f2e1a501c8cbff01cf16c9f86981783a70f8446f03fe2030f849f848f847f846f844f842c8cbfff843cf16cb07f845cf16ccccccccc9ed543dca7f27' + codebase_version + + def __init__(self, **kwargs): + kwargs['code'] = Cell.one_from_boc(self.code) + super().__init__(**kwargs) + + def create_royalties_dict(self): + royalties_dict = begin_dict(8) + i = 0 + for address in self.options['royalties']: + royalties_dict.store_ref( + i, begin_cell() \ + .store_address(Address(address)) \ + .store_uint(int((self.options['royalties'][address] / 100) * 1000), 16) \ + .end_cell() + ) + i += 1 + + return royalties_dict.end_dict() + + def create_data_cell(self): # not actual + return begin_cell() \ + .store_uint(self.options['content_hash'], 256) \ + .store_address(Address(MY_PLATFORM_CONTRACT)) \ + .store_address(self.options['owner_address']) \ + .store_ref( + begin_cell() + .store_uint(0, 1) + .store_uint(0, 64) + .store_address(None) + .end_cell() + ) \ + .store_maybe_ref(self.create_royalties_dict()) \ + .store_ref( + begin_cell() + .store_ref(self.options['metadata']) + .store_ref(self.options['content']) + .end_cell() + ).end_cell() + + diff --git a/app/core/_blockchain/ton/contracts/platform.py b/app/core/_blockchain/ton/contracts/platform.py new file mode 100644 index 0000000..18fd243 --- /dev/null +++ b/app/core/_blockchain/ton/contracts/platform.py @@ -0,0 +1,36 @@ +from tonsdk.boc import Cell, begin_cell +from tonsdk.contract import Contract + + +class Platform(Contract): + code = 'b5ee9c724102180100039f000114ff00f4a413f4bcf2c80b01020120030200a2f28308d71831d33f01f823bbf264d31f30810e7fba8e39f8007082f0be521f12758263c40b0dc6bdd1f5a26dbc8c7b7c349068647f749da5a716aea521748010c8cb05cb02cb07cbffcb6ac98306fb00e00201480f0402012008050201200706004bbac877382f06949f6f27c3c5650394e5f9b14a79bc4d647b8fe4870c0014ece6b05cb2bd74780057b905bed44d0fa4001f861d3ff01f862d401f863f843d0d431d430f864d401f865d1f845d0f84201d430f84180201200c090201200b0a00a1b4f47da89a1f48003f0c3a7fe03f0c5a803f0c7f087a1a863a861f0c9a803f0cba3f089f050e0079197ff92826190a0079e2d960f9992e04191960227e801e801960193f200e0e9919605940f97ff93a10000fb5daee0419193a100201200e0d0059b6a9bda89a1f48003f0c3a7fe03f0c5a803f0c7f087a1a863a861f0c9a803f0cba2e1f051f085f087f089f08b00051b56ba63da89a1f48003f0c3a7fe03f0c5a803f0c7f087a1a863a861f0c9a803f0cba2e391960f999300202c711100007a0dd7c120201cf131200113e910c30003cb8536002f30cf434c0c05c6c2497c0f83e90087c007e900c7e800c5c75c87e800c7e800c1cea6d0008f5d27048245c2540f4c7d411388830002497c1783b51343e90007e1874ffc07e18b5007e18fe10f4350c750c3e1935007e1974482084091ea7aeaea497c178082084152474232ea3a14c104c36cf380c4cbe1071c160161401c4f2e19120820833cc77ba9730d4d30730fb00e0208210b99cd03bba9701fa4001f86101de208210d81c632fba9601d401f86501de208210b5de5f9ebae30282102fa30f96ba98d401fb04d430ed54e030f845f843f842c8f841cf16cbffccccc9ed5415008a30fa40fa00306d6d71708010c8cb055006cf165004fa0214cb68216e947032cb019bc858cf17c97158cb00f400e2226e95327058cb0099c85003cf17c958f400e2c901fb0001e4821005f5e10001a013bef2e20801d3ffd4d430f844f82870f842c8cbffc9c85003cf16cb07ccc97020c8cb0113f400f400cb00c920f9007074c8cb02ca07cbffc9d0f843d070c804d014cf16f843f842c8cbfff828cf16c903d430c8cc13ccc9c8cc17cbff5007cf1614cc15cccc433080401700a6708010c8cb055006cf165004fa0214cb68216e947032cb019bc858cf17c97158cb00f400e2226e95327058cb0099c85003cf17c958f400e2c901fb00f842a4f862f845f843f842c8f841cf16cbffccccc9ed54f7fbeaf8' + + def __init__(self, **kwargs): + kwargs['code'] = Cell.one_from_boc(self.code) + super().__init__(**kwargs) + + def create_content_cell(self) -> Cell: + return ( + begin_cell() + .store_ref( + begin_cell() + .store_uint(1, 8) + .store_bytes(self.options['collection_content_uri'].encode()) + .end_cell() + ) + .store_ref(begin_cell().end_cell()) + .end_cell() + ) + + def create_data_cell(self) -> Cell: + return begin_cell() \ + .store_address(self.options['admin_address']) \ + .store_uint(0, 256) \ + .store_ref( + begin_cell() + .store_ref(self.options['cop_code']) + .store_ref(self.options['blank_code']) + .end_cell() + ) \ + .store_ref(self.create_content_cell()) \ + .end_cell() diff --git a/app/core/_blockchain/ton/platform.py b/app/core/_blockchain/ton/platform.py new file mode 100644 index 0000000..0a584f4 --- /dev/null +++ b/app/core/_blockchain/ton/platform.py @@ -0,0 +1,20 @@ +from app.core._config import TESTNET, MY_PLATFORM_CONTRACT +from app.core._secrets import service_wallet +from app.core._blockchain.ton.contracts.platform import Platform +from app.core._blockchain.ton.contracts.cop_nft import COP_NFT +from app.core._blockchain.ton.contracts.blank import Blank +from tonsdk.boc import Cell +from tonsdk.utils import Address + +kwargs = {} +if TESTNET is False: + kwargs['address'] = Address(MY_PLATFORM_CONTRACT) + +platform = Platform( + admin_address=service_wallet.address, + blank_code=Cell.one_from_boc(Blank.code), + cop_code=Cell.one_from_boc(COP_NFT.code), + + collection_content_uri='https://music-gateway.letsw.app/api/platform-metadata.json', + **kwargs +) diff --git a/app/core/_config.py b/app/core/_config.py index 4de628b..5dbf152 100644 --- a/app/core/_config.py +++ b/app/core/_config.py @@ -37,7 +37,11 @@ ALLOWED_CONTENT_TYPES = [ 'audio/mpeg', 'audio/ogg', 'audio/wav', ] +TESTNET = bool(int(os.getenv('TESTNET', '0'))) + TONCENTER_HOST = os.getenv('TONCENTER_HOST', 'https://toncenter.com/api/v1/') TONCENTER_API_KEY = os.getenv('TONCENTER_API_KEY') +# 706a0565aae1e89968aea905043f1b6e150a5b80130ce92a57b4904c82865b43 +MY_PLATFORM_CONTRACT = 'EQAGbwW0sFghy9N4MQ0Ozp8YOIr0lcMI8J5kbbydFnQtheMY' MY_FUND_ADDRESS = 'UQDarChHFMOI2On9IdHJNeEKttqepgo0AY4bG1trw8OAAwMY' diff --git a/app/core/models/node_storage.py b/app/core/models/node_storage.py index 2293958..2f1e06f 100644 --- a/app/core/models/node_storage.py +++ b/app/core/models/node_storage.py @@ -11,15 +11,17 @@ class StoredContent(AlchemyBase): __tablename__ = 'node_storage' id = Column(Integer, autoincrement=True, primary_key=True) + type = Column(String(32), nullable=False) hash = Column(String(64), nullable=False, unique=True) # base58 onchain_index = Column(BigInteger, nullable=True, default=None) + status = Column(String(32), nullable=True) filename = Column(String(1024), nullable=False) meta = Column(JSON, nullable=False, default={}) user_id = Column(Integer, ForeignKey('users.id'), nullable=False) - storj_cid = Column(String(1024), nullable=False, unique=True) + btfs_cid = Column(String(1024), nullable=False, unique=True) ipfs_cid = Column(String(1024), nullable=False, unique=True) telegram_cid = Column(String(1024), nullable=False, unique=True) diff --git a/requirements.txt b/requirements.txt index a967f86..498cc50 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ pytonconnect==0.3.0 base58==2.1.1 tonsdk==1.0.13 httpx==0.25.0 +docker==7.0.0