ergoplatform / ergo-appkit Goto Github PK
View Code? Open in Web Editor NEWAppkit: A Library for Java/Scala/Kotlin Development of Ergo Applications
License: MIT License
Appkit: A Library for Java/Scala/Kotlin Development of Ergo Applications
License: MIT License
Currently, the cost limit is extracted from blockchain. I think there should be an ability to manually declare it.
BlockChainContext.sendTransaction
returns null
or an empty string if no transaction could be made. However, it would be good to let the calling application know what went wrong. This could be done by throwing an error containing Retrofit's call object with more information.
Created from ergo-lib #495
Using NiPoPoW described in KMZ17
Prove that a payment (ERG and/or tokens) was made on the blockchain.
EDIT: For a recent payment it would be faster to use UTXO proof described in #502
1.1.1. Ask proofs from known nodes via /nipopow/proof/{m}/{k}/{headerId}
.
1.1.2. Feed all received proofs into NipopowVerifier.process()
.
1.1.3. Check that verifier.bestProof.suffixHead.id
is our block id;
1.2.1. Ask any node for MerkleProof
via /blocks/{headerId}/proofFor/{txId}
;
1.2.2. Confirm the tx id is in the block with merkleProof.valid(header.transactionsRoot)
;
See Step 1.1 in ergo-lib example workflow
It can either be provided along with the tx id in Step 1 or fetched from the node. It is verified by serializing the tx and calculating its id and then comparing it to the tx id confirmed in Step 1.2.
Find the output box protected with a certain script (e.g certain PK) and check that its value and/or tokens are as expected.
NipopowProof
, NipopowVerifier
, and all dependent types from node code into a lib NipopowProof
, NipopowVerifier
, MerkleProof
with mentioned above properties/methods in appkit public API.EDIT: Actually, I'm pretty sure that the above NiPoPoW-related types would better be in a dedicated ergo-nipopow lib since they are not directly related to wallet features. However, the bigger challenge will be the dependent more low-level types like Header
, etc. Seems like they should be moved to their own separate lib (ergo-core/ergo-chain-types?).
In some cases boxes can be created with 2 entries for the same token, for example on entry with 2k migoreng and one entry with 6k migoreng.
The appkit seems to only count one of these entries when creating the change box, causing the rest to be burned.
An example of a box with duplicate token entries can be seen in the output of this tx:
https://explorer.ergoplatform.com/en/transactions/02b425ed91ca655cb39f83febbcd0efed6114a05f8b7eb69324042b2372060c5
Each CliApplication
implements a list of commands.
This list can be printed in console output as part of usage help message.
However, it is usually required to have this list also in a documentation page.
The corresponding section of the doc page can thus be generated for any CliApplication instance.
Implement the genMarkdown
method which uses commands
method for a list of supported commands.
Construction of ErgoValue instances with complex types of value is problematic, because ErgoValue is implemented with Java types, which makes it easier to be used from Java.
This issue is to implement generic builder
val x: Coll[(Coll[(Byte, Long)], Coll[Byte])] = ...
val ev = ErgoValueBuilder.buildFor(x)
#48 is a pre-requisite
Check that it plays well with native-image generation.
Some of my pure p2s transactions reduce to false with the appkit but sign fine using the node.
Testnet example:
UnsignedTx:
{'inputs': [{'boxId': '6ee4d5d0dc518d52386cef251efa732680a8600ecac87ee3ad95c7aa1d8282b3', 'value': '1000000', 'ergoTree': '104e0402040004020400040204000400040204020404040404060406040004000408040404080e20ec7db8b46327614a657fe8f7391a9c264d86b7aca5832d52148ce1e38436dc6b0e20b64a4682c11a4f02a50ba8f7d6587daebb93e55199f33a108c987ffb2cbd16f30500040204000406040404020404040005020502040004020580dddb01050205d00f040205020402040204020404040004000502040404000402050201000e208557834f67bdc177ee9528d7d0faf33ef025666f1f45a70e7e57d32f327dcec40404050204000e2083759b1a3d080d043db81acd10e1a6cb5b6fff8815b32c0ccc3380a9691ad909040205020404050001000402040204000500040404000502050005020500050004000402040004020402040205d00f0100d829d601b2a5730000d602db63087201d603b27202730100d6048c720301d605db6308a7d606b27205730200d6078c720601d6089372047207d609b2a5730300d60adb63087209d60bb2720a730400d60c8c720b02d60d8c720602d60ee4c6a70411d60fb2720e730500d610e4c672090411d611b27210730600d612b27210730700d613b2720e730800d614b2720e730900d615b27210730a00d616b27210730b00d617b2720e730c00d618b2720a730d00d619b27205730e00d61ab2720e730f00d61b9683070193c17209c1a793c27209c2a7938c7218018c721901938c7218028c721902938c720b01720793b1720a731093b27210731100721ad61c7312d61ddb6903db6503fed61e8c720302d61f7313d62086028300027314d621b2a4731500d622db63087221d623b27222731600d6248c722301d62592b1a47317d626e4c672210411d627e4c67221050ed628b2a5731800d629db63087228d1ecec957208958f720c720dd806d62ab27202731900d62b8c722a02d62ce4c672010411d62de4c67201050ed62eb2a5731a00d62fb2db6308722e731b009683030196830601721b9372119a720f722b93721272139372159a7214731c937216721793720c99720d731d9683080193cbc27201721c93b2722c731e00721392b2722c731f0099721d732093722dc5a7720893721e7321938c722a01721f92722b73229683030193c2722ee4c6b2a4732300050e938c722f01722d938c722f027324d807d62ab27202732500d62b8c722a02d62cb2a4732600d62d8cb2db6308722c732701722002d62ee4c672010411d62fe4c67201050ed630b2db6308b2a57328007329009683030196830601721b9372119a720f99722b722d93721272139372157214937216721793720c720d96830a0193c17201c1722c93cbc27201721c93cbc2722c721c93b2722e732a00721393722ee4c6722c041193722fe4c6722c050e720893721e732b938c722a01721f93722b9a722d8cb2db6308b2a4732c01b2a4732d00732e000296830201938c723001722f938c723002732f7330959683020193722473317225d802d62ab2a4733200d62be4c6722a04119683020196830601721b9372129a7213733393721572149372169a7217721a8f7216721d93720c720d96830301938cb2db6308722a73340001733593b2722b733600997213733793b2722b7338007339733a959683030191720f7211722591b17222733bd807d62ab27222733c00d62b99720f7211d62c998c722a02722bd62d8c722a01d62eb27226733d00d62f90722c733ed6309683040196830201937224720793722e7213938cb2db6308b2a4733f0073400001722796830601721b93721199720f722b937212721393721599721495722f73417342937216721793720c9a720d95722f7343734496830201937204722d93721e722b9591722c7345d803d631b272297346017220d632b27229734700d633e4c672280411968303017230968302019683080193c17228c1722193c27228c27221938c7231017224938c7231028c722302938c723201722d938c723202722c93b27233734800722e93b27233734900b27226734a00938cb27202734b01722001722792722c734c7230734d', 'creationHeight': 257321, 'assets': [{'tokenId': '99272858f97ca9c1e6a313f1fb4c5e27912e0a7a5a76ceb065e52c3c11b6e647', 'amount': '1'}, {'tokenId': '21038855e00a9e767ffbeb9bbc1f59c39555e9e11652dcb5a0f689b53d78b197', 'amount': '999999999988'}], 'additionalRegisters': {'R4': '110580d30e00188098fe96a56080f0b252'}, 'transactionId': '423909c8e0f1354694bc7a74711cc41e10b577f36d1608e89454983ed389276e', 'index': 0, 'extension': {}}, {'boxId': 'a9322bc7e71916285855645ef65bd873c48e840ca4ca0e341fdb50721ced1375', 'value': '1000000', 'ergoTree': '101d040004000e2099272858f97ca9c1e6a313f1fb4c5e27912e0a7a5a76ceb065e52c3c11b6e6470402040204020404040404020500040005c80104060400040004020402040004000400040204000e240008cd02189359b825e96aa3c7af90c9958d85daf8f86358382db3306e024c5aeea1e8ec0580897a01010100040004000100d802d601b2a4730000d602c5a7d1ec9596830201938cb2db6308720173010001730293c5b2a47303007202d80cd603b2a5730400d604db63087203d605db6308a7d606b27205730500d607db6308b2a4730600d6089592b1720773078cb27207730800027309d6099a8c7206027208d60ae4c6a70411d60bb2720a730a00d60c8c720601d60d9d720b730bd60eb2a5730c00968302019683070193c17203c1a793c27203c2a7938cb27204730d00018cb27205730e000195917209720bd801d60fb27204730f0096830201938c720f01720c938c720f02997209720b93b17204731093e4c672030411720a93e4c67203050ee4c6a7050e93b2e4c6b2a57311000411731200999ab2e4c67201041173130099720b720d72089591b172047314d801d60fb2db6308720e7315009683040193c2720e731693c1720e7317938c720f01720c938c720f02720d731873199593c572017202938cb2db6308b2a5731a00731b0001e4c6a7050e731c', 'creationHeight': 257244, 'assets': [{'tokenId': '8557834f67bdc177ee9528d7d0faf33ef025666f1f45a70e7e57d32f327dcec4', 'amount': '1'}, {'tokenId': 'b64a4682c11a4f02a50ba8f7d6587daebb93e55199f33a108c987ffb2cbd16f3', 'amount': '50000000000'}], 'additionalRegisters': {'R4': '1101a0cda38502', 'R5': '0e2014d5b543223eb8e67ea70c1884118122c77dfb0020140fc93cedded89186e582'}, 'transactionId': '70201e1fbd3067905ac77b7a3e58076b78995e1e9a347a303fd7d0bef2d7e7af', 'index': 1, 'extension': {}}, {'boxId': '0361723110b35154298317f604d645af873060970067b9867c4b835225063a3a', 'value': '1000000', 'ergoTree': '102d0400040404020e20b64a4682c11a4f02a50ba8f7d6587daebb93e55199f33a108c987ffb2cbd16f3040004000e2099272858f97ca9c1e6a313f1fb4c5e27912e0a7a5a76ceb065e52c3c11b6e6470404040604000402040205000402040004060400040605c8010400040001000402040004000e2021038855e00a9e767ffbeb9bbc1f59c39555e9e11652dcb5a0f689b53d78b197040004020100040606010004020400040004000402040204000404040004040404040404060100d807d601b2a4730000d602c5a7d603b2a5730100d604b2a4730200d6057303d606b2a5730400d607db6308a7d1ec9596830201938cb2db6308720173050001730693c5b2a47307007202d807d608e4c672030411d609b27208730800d60ab2e4c672040411730900d60bdb6308a7d60c9a8cb2db63087204730a00028cb2720b730b018602830002730c02d60ddb63087203d60eb2720d730d009683070193c17203c1a793c27203c2a793b47208730e730fb4e4c67201041173107311937209958f720a720c99720a9d720a7312720c938cb2720d731300018cb2720b73140001938c720e017205938c720e02720973159593c572017202d807d608db63087206d6097e8cb272077316000206d60ab5a4d9010a63d801d60cdb6308720a9591b1720c7317ed938cb2720c73180001731993b2e4c6720a0411731a00b2e4c6a70411731b00731cd60be4c6a70411d60cb2720b731d00d60db0720a731ed9010d42639a8c720d019d9c7e8cb2db63088c720d02731f0002067e720c067eb2720b73200006d60ee4c6720604119683070193c17206c1a793c27206c2a7938cb27208732100018cb272077322000195907209720d93b172087323d801d60fb27208732400ed938c720f017205927e8c720f0206997209720d93b4720e73257326b4720b7327732893b2720e73290099b2720b732a007eb1720a0593b2720e732b00720c732c', 'creationHeight': 257244, 'assets': [{'tokenId': '83759b1a3d080d043db81acd10e1a6cb5b6fff8815b32c0ccc3380a9691ad909', 'amount': '1'}], 'additionalRegisters': {'R4': '1104000100a0cda38502'}, 'transactionId': '70201e1fbd3067905ac77b7a3e58076b78995e1e9a347a303fd7d0bef2d7e7af', 'index': 2, 'extension': {}}, {'boxId': '33c2e2f9e6c9cbb6f3e8e9290deaee980d8c0ac202f2272cb85cb27bc6344cf1', 'value': '100000000', 'ergoTree': '102f048084af5f04060400040205c0843d04040580897a010005000e2083759b1a3d080d043db81acd10e1a6cb5b6fff8815b32c0ccc3380a9691ad9090406040004000e2099272858f97ca9c1e6a313f1fb4c5e27912e0a7a5a76ceb065e52c3c11b6e6470404040004020100040805809bee020580897a0580897a040a05809bee02040c0580897a0100040004020400040004000e2021038855e00a9e767ffbeb9bbc1f59c39555e9e11652dcb5a0f689b53d78b19704000402010005c0843d05e0a7120580897a05c09a0c040404020100040004000e2014d5b543223eb8e67ea70c1884118122c77dfb0020140fc93cedded89186e5820100d80ad601c1a7d60295968302019072017e73000593b1a57301d802d602b2a5730200d603c2a7968303019683020191c17202720193c27202720393c1b2a57303009c73047eb1b5a4d901046393c2720472030593c1b2a573050073067307d603b1a4d60486028300027308d6057309d606c2a7d6079595927203730a96830301938cb2db6308b2a4730b00730c01720401730d938cb2db6308b2a4730e00730f01720401720593c5b2a4997203731000c5a77311d801d607b2a5731200968303019683020192c17207999999720173137314731593c27207720693c1b2a5731600731793c1b2a57318007319731ad608b2a4731b00d609997203731cd60a9596830201938cb2db63087208731d01720401720593c5b2a4720900c5a7d805d60ab1b5a4d9010a63d801d60cdb6308720a9591b1720c731e96830201938cb2720c731f0001732093b2e4c6720a0411732100b2e4c6720804117322007323d60bb2a5720900d60c7e720a05d60d9a73249c7325720cd60e9a73269c7327720c968304019372039a720a73289683020192c1720b99997201720d720e93c2720b720693c1b2a5720300720d93c1b2a59a7203732900720e732ad1ececec72027207720a95efecec72027207720a938cb2db6308b2a5732b00732c0001732d732e', 'creationHeight': 257321, 'assets': [], 'additionalRegisters': {}, 'transactionId': '423909c8e0f1354694bc7a74711cc41e10b577f36d1608e89454983ed389276e', 'index': 3, 'extension': {}}], 'dataInputs': [], 'outputs': [{'value': '1000000', 'ergoTree': '104e0402040004020400040204000400040204020404040404060406040004000408040404080e20ec7db8b46327614a657fe8f7391a9c264d86b7aca5832d52148ce1e38436dc6b0e20b64a4682c11a4f02a50ba8f7d6587daebb93e55199f33a108c987ffb2cbd16f30500040204000406040404020404040005020502040004020580dddb01050205d00f040205020402040204020404040004000502040404000402050201000e208557834f67bdc177ee9528d7d0faf33ef025666f1f45a70e7e57d32f327dcec40404050204000e2083759b1a3d080d043db81acd10e1a6cb5b6fff8815b32c0ccc3380a9691ad909040205020404050001000402040204000500040404000502050005020500050004000402040004020402040205d00f0100d829d601b2a5730000d602db63087201d603b27202730100d6048c720301d605db6308a7d606b27205730200d6078c720601d6089372047207d609b2a5730300d60adb63087209d60bb2720a730400d60c8c720b02d60d8c720602d60ee4c6a70411d60fb2720e730500d610e4c672090411d611b27210730600d612b27210730700d613b2720e730800d614b2720e730900d615b27210730a00d616b27210730b00d617b2720e730c00d618b2720a730d00d619b27205730e00d61ab2720e730f00d61b9683070193c17209c1a793c27209c2a7938c7218018c721901938c7218028c721902938c720b01720793b1720a731093b27210731100721ad61c7312d61ddb6903db6503fed61e8c720302d61f7313d62086028300027314d621b2a4731500d622db63087221d623b27222731600d6248c722301d62592b1a47317d626e4c672210411d627e4c67221050ed628b2a5731800d629db63087228d1ecec957208958f720c720dd806d62ab27202731900d62b8c722a02d62ce4c672010411d62de4c67201050ed62eb2a5731a00d62fb2db6308722e731b009683030196830601721b9372119a720f722b93721272139372159a7214731c937216721793720c99720d731d9683080193cbc27201721c93b2722c731e00721392b2722c731f0099721d732093722dc5a7720893721e7321938c722a01721f92722b73229683030193c2722ee4c6b2a4732300050e938c722f01722d938c722f027324d807d62ab27202732500d62b8c722a02d62cb2a4732600d62d8cb2db6308722c732701722002d62ee4c672010411d62fe4c67201050ed630b2db6308b2a57328007329009683030196830601721b9372119a720f99722b722d93721272139372157214937216721793720c720d96830a0193c17201c1722c93cbc27201721c93cbc2722c721c93b2722e732a00721393722ee4c6722c041193722fe4c6722c050e720893721e732b938c722a01721f93722b9a722d8cb2db6308b2a4732c01b2a4732d00732e000296830201938c723001722f938c723002732f7330959683020193722473317225d802d62ab2a4733200d62be4c6722a04119683020196830601721b9372129a7213733393721572149372169a7217721a8f7216721d93720c720d96830301938cb2db6308722a73340001733593b2722b733600997213733793b2722b7338007339733a959683030191720f7211722591b17222733bd807d62ab27222733c00d62b99720f7211d62c998c722a02722bd62d8c722a01d62eb27226733d00d62f90722c733ed6309683040196830201937224720793722e7213938cb2db6308b2a4733f0073400001722796830601721b93721199720f722b937212721393721599721495722f73417342937216721793720c9a720d95722f7343734496830201937204722d93721e722b9591722c7345d803d631b272297346017220d632b27229734700d633e4c672280411968303017230968302019683080193c17228c1722193c27228c27221938c7231017224938c7231028c722302938c723201722d938c723202722c93b27233734800722e93b27233734900b27226734a00938cb27202734b01722001722792722c734c7230734d', 'assets': [{'tokenId': '99272858f97ca9c1e6a313f1fb4c5e27912e0a7a5a76ceb065e52c3c11b6e647', 'amount': '1'}, {'tokenId': '21038855e00a9e767ffbeb9bbc1f59c39555e9e11652dcb5a0f689b53d78b197', 'amount': '999999999988'}], 'additionalRegisters': {'R4': '1105b8e8e3820202188088b1e9a56080f0b252'}, 'creationHeight': 257330}, {'value': '1000000', 'ergoTree': '101d040004000e2099272858f97ca9c1e6a313f1fb4c5e27912e0a7a5a76ceb065e52c3c11b6e6470402040204020404040404020500040005c80104060400040004020402040004000400040204000e240008cd02189359b825e96aa3c7af90c9958d85daf8f86358382db3306e024c5aeea1e8ec0580897a01010100040004000100d802d601b2a4730000d602c5a7d1ec9596830201938cb2db6308720173010001730293c5b2a47303007202d80cd603b2a5730400d604db63087203d605db6308a7d606b27205730500d607db6308b2a4730600d6089592b1720773078cb27207730800027309d6099a8c7206027208d60ae4c6a70411d60bb2720a730a00d60c8c720601d60d9d720b730bd60eb2a5730c00968302019683070193c17203c1a793c27203c2a7938cb27204730d00018cb27205730e000195917209720bd801d60fb27204730f0096830201938c720f01720c938c720f02997209720b93b17204731093e4c672030411720a93e4c67203050ee4c6a7050e93b2e4c6b2a57311000411731200999ab2e4c67201041173130099720b720d72089591b172047314d801d60fb2db6308720e7315009683040193c2720e731693c1720e7317938c720f01720c938c720f02720d731873199593c572017202938cb2db6308b2a5731a00731b0001e4c6a7050e731c', 'assets': [{'tokenId': '8557834f67bdc177ee9528d7d0faf33ef025666f1f45a70e7e57d32f327dcec4', 'amount': '1'}, {'tokenId': 'b64a4682c11a4f02a50ba8f7d6587daebb93e55199f33a108c987ffb2cbd16f3', 'amount': '49726030000'}], 'additionalRegisters': {'R4': '1101a0cda38502', 'R5': '0e2014d5b543223eb8e67ea70c1884118122c77dfb0020140fc93cedded89186e582'}, 'creationHeight': 257330}, {'value': '1000000', 'ergoTree': '102d0400040404020e20b64a4682c11a4f02a50ba8f7d6587daebb93e55199f33a108c987ffb2cbd16f3040004000e2099272858f97ca9c1e6a313f1fb4c5e27912e0a7a5a76ceb065e52c3c11b6e6470404040604000402040205000402040004060400040605c8010400040001000402040004000e2021038855e00a9e767ffbeb9bbc1f59c39555e9e11652dcb5a0f689b53d78b197040004020100040606010004020400040004000402040204000404040004040404040404060100d807d601b2a4730000d602c5a7d603b2a5730100d604b2a4730200d6057303d606b2a5730400d607db6308a7d1ec9596830201938cb2db6308720173050001730693c5b2a47307007202d807d608e4c672030411d609b27208730800d60ab2e4c672040411730900d60bdb6308a7d60c9a8cb2db63087204730a00028cb2720b730b018602830002730c02d60ddb63087203d60eb2720d730d009683070193c17203c1a793c27203c2a793b47208730e730fb4e4c67201041173107311937209958f720a720c99720a9d720a7312720c938cb2720d731300018cb2720b73140001938c720e017205938c720e02720973159593c572017202d807d608db63087206d6097e8cb272077316000206d60ab5a4d9010a63d801d60cdb6308720a9591b1720c7317ed938cb2720c73180001731993b2e4c6720a0411731a00b2e4c6a70411731b00731cd60be4c6a70411d60cb2720b731d00d60db0720a731ed9010d42639a8c720d019d9c7e8cb2db63088c720d02731f0002067e720c067eb2720b73200006d60ee4c6720604119683070193c17206c1a793c27206c2a7938cb27208732100018cb272077322000195907209720d93b172087323d801d60fb27208732400ed938c720f017205927e8c720f0206997209720d93b4720e73257326b4720b7327732893b2720e73290099b2720b732a007eb1720a0593b2720e732b00720c732c', 'assets': [{'tokenId': '83759b1a3d080d043db81acd10e1a6cb5b6fff8815b32c0ccc3380a9691ad909', 'amount': '1'}, {'tokenId': 'b64a4682c11a4f02a50ba8f7d6587daebb93e55199f33a108c987ffb2cbd16f3', 'amount': '271230300'}], 'additionalRegisters': {'R4': '110480d30e0018b895d58202'}, 'creationHeight': 257330}, {'value': '1000000', 'ergoTree': '0008cd02189359b825e96aa3c7af90c9958d85daf8f86358382db3306e024c5aeea1e8ec', 'assets': [{'tokenId': 'b64a4682c11a4f02a50ba8f7d6587daebb93e55199f33a108c987ffb2cbd16f3', 'amount': '2739700'}], 'additionalRegisters': {}, 'creationHeight': 257330}, {'value': '95000000', 'ergoTree': '102f048084af5f04060400040205c0843d04040580897a010005000e2083759b1a3d080d043db81acd10e1a6cb5b6fff8815b32c0ccc3380a9691ad9090406040004000e2099272858f97ca9c1e6a313f1fb4c5e27912e0a7a5a76ceb065e52c3c11b6e6470404040004020100040805809bee020580897a0580897a040a05809bee02040c0580897a0100040004020400040004000e2021038855e00a9e767ffbeb9bbc1f59c39555e9e11652dcb5a0f689b53d78b19704000402010005c0843d05e0a7120580897a05c09a0c040404020100040004000e2014d5b543223eb8e67ea70c1884118122c77dfb0020140fc93cedded89186e5820100d80ad601c1a7d60295968302019072017e73000593b1a57301d802d602b2a5730200d603c2a7968303019683020191c17202720193c27202720393c1b2a57303009c73047eb1b5a4d901046393c2720472030593c1b2a573050073067307d603b1a4d60486028300027308d6057309d606c2a7d6079595927203730a96830301938cb2db6308b2a4730b00730c01720401730d938cb2db6308b2a4730e00730f01720401720593c5b2a4997203731000c5a77311d801d607b2a5731200968303019683020192c17207999999720173137314731593c27207720693c1b2a5731600731793c1b2a57318007319731ad608b2a4731b00d609997203731cd60a9596830201938cb2db63087208731d01720401720593c5b2a4720900c5a7d805d60ab1b5a4d9010a63d801d60cdb6308720a9591b1720c731e96830201938cb2720c731f0001732093b2e4c6720a0411732100b2e4c6720804117322007323d60bb2a5720900d60c7e720a05d60d9a73249c7325720cd60e9a73269c7327720c968304019372039a720a73289683020192c1720b99997201720d720e93c2720b720693c1b2a5720300720d93c1b2a59a7203732900720e732ad1ececec72027207720a95efecec72027207720a938cb2db6308b2a5732b00732c0001732d732e', 'assets': [], 'additionalRegisters': {}, 'creationHeight': 257330}, {'value': '3000000', 'ergoTree': '0008cd03d9d11f15c9564a310361e6c0edd34be6838d3cd811ec449f1bbce334599e0d01', 'assets': [], 'additionalRegisters': {}, 'creationHeight': 257330}, {'value': '1000000', 'ergoTree': '1005040004000e36100204900108cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304', 'assets': [], 'additionalRegisters': {}, 'creationHeight': 257330}]}
A small whale got the error "There should be no duplicate inputs" when sending a big amount of ERGs.
Investigating the error turned out that BlockchainContextImpl.getUnspentBoxesFor
with offset 20 returned some boxes that were already returned for offset 0. It looks like Ergo Explorer returned results are not disjoint:
Offset 0:
15 = {OutputInfo@28022} "class OutputInfo {\n boxId: 12acc1cfcc6b9c22170fbeb13852dc866add291b92eed67e37cbda2a8fa29796\n transactionId: 58d04ade811523d952a0bfc35fe77d34f9915b6c2e0fe430a960d40a9d2ee6be\n blockId: 84d0dd691d73fe7e731e2d8449307617cd9a9a9b8c827697e2a7ce54635cbf8c\n value: 10000000000\n index: 0\n creationHeight: 597741\n settlementHeight: 597743\n ergoTree: 0008cd03ebf2124bc8da317bf0152e416594df3662a5d6e947d75920c228b368bbca245b\n address: 9iFic39ctD5pngbHWX3WFipEJDRAZ2wyHircd8pCqGycHd7VrxX\n assets: []\n spentTransactionId: null\n mainChain: true\n}"
16 = {OutputInfo@28023} "class OutputInfo {\n boxId: bb3e14bf5a75fc52441caa281a6c84e92091d1d45af9f421838bc51e61e813d4\n transactionId: 8ab3e4e42533aef2746515fa567a07ac31006891be365e51b9afb74e1d00692e\n blockId: 84d0dd691d73fe7e731e2d8449307617cd9a9a9b8c827697e2a7ce54635cbf8c\n value: 10000000000\n index: 0\n creationHeight: 597741\n settlementHeight: 597743\n ergoTree: 0008cd03ebf2124bc8da317bf0152e416594df3662a5d6e947d75920c228b368bbca245b\n address: 9iFic39ctD5pngbHWX3WFipEJDRAZ2wyHircd8pCqGycHd7VrxX\n assets: []\n spentTransactionId: null\n mainChain: true\n}"
17 = {OutputInfo@28024} "class OutputInfo {\n boxId: 7d8448ce645499ba6373d626fa264502dbfca329f108c0c31425b0176281fa9a\n transactionId: 7424e7000a0d9ada39744cd11b43fdca43a009a2a3170fca5b7d4d729ea3c005\n blockId: 84d0dd691d73fe7e731e2d8449307617cd9a9a9b8c827697e2a7ce54635cbf8c\n value: 10000000000\n index: 0\n creationHeight: 597741\n settlementHeight: 597743\n ergoTree: 0008cd03ebf2124bc8da317bf0152e416594df3662a5d6e947d75920c228b368bbca245b\n address: 9iFic39ctD5pngbHWX3WFipEJDRAZ2wyHircd8pCqGycHd7VrxX\n assets: []\n spentTransactionId: null\n mainChain: true\n}"
18 = {OutputInfo@28025} "class OutputInfo {\n boxId: 6ea1f6c064dd8ad6a01dff9378a71ec0e8b4194e4d7d5f8e04b4150d27f14d85\n transactionId: 513d31a418f8a24df2bae0b9a10aed859dc854c503e36f9e9660cc61b7ad86c9\n blockId: 84d0dd691d73fe7e731e2d8449307617cd9a9a9b8c827697e2a7ce54635cbf8c\n value: 10000000000\n index: 0\n creationHeight: 597741\n settlementHeight: 597743\n ergoTree: 0008cd03ebf2124bc8da317bf0152e416594df3662a5d6e947d75920c228b368bbca245b\n address: 9iFic39ctD5pngbHWX3WFipEJDRAZ2wyHircd8pCqGycHd7VrxX\n assets: []\n spentTransactionId: null\n mainChain: true\n}"
19 = {OutputInfo@28026} "class OutputInfo {\n boxId: 10fd280e5d00a4735a0bfedbd5608278b929a19246316b56b5028b8fcb37cf75\n transactionId: d8e68b2b84aede4fd00ac21d60388bdb3539f2c1d554acf258a4f51d9c6d25ef\n blockId: 84d0dd691d73fe7e731e2d8449307617cd9a9a9b8c827697e2a7ce54635cbf8c\n value: 10000000000\n index: 0\n creationHeight: 597741\n settlementHeight: 597743\n ergoTree: 0008cd03ebf2124bc8da317bf0152e416594df3662a5d6e947d75920c228b368bbca245b\n address: 9iFic39ctD5pngbHWX3WFipEJDRAZ2wyHircd8pCqGycHd7VrxX\n assets: []\n spentTransactionId: null\n mainChain: true\n}"
Offset 20:
0 = {OutputInfo@28548} "class OutputInfo {\n boxId: 98f5c7c486dea53653286a826d157ba3aa736686035b274d80177805c75cf322\n transactionId: a4bd4e7f81dae310423b8b8bdcaa7e87338c18bbbb875800e11228638fd66bb5\n blockId: 84d0dd691d73fe7e731e2d8449307617cd9a9a9b8c827697e2a7ce54635cbf8c\n value: 10000000000\n index: 0\n creationHeight: 597741\n settlementHeight: 597743\n ergoTree: 0008cd03ebf2124bc8da317bf0152e416594df3662a5d6e947d75920c228b368bbca245b\n address: 9iFic39ctD5pngbHWX3WFipEJDRAZ2wyHircd8pCqGycHd7VrxX\n assets: []\n spentTransactionId: null\n mainChain: true\n}"
1 = {OutputInfo@28549} "class OutputInfo {\n boxId: 042d4325821ceeeb2e4e55c066c370ce76e40cbdccf758953a55dbb87f10c61b\n transactionId: 6e093381ff743fb1232f72355bfbb1c24d7abf20e713e1a6dc58b77a34df7f83\n blockId: 5f846f165b4e5a09b3d1feff6c8358c89ffc70df0e15fc3b3a97b90d17ee4861\n value: 10000000000\n index: 0\n creationHeight: 597741\n settlementHeight: 597744\n ergoTree: 0008cd03ebf2124bc8da317bf0152e416594df3662a5d6e947d75920c228b368bbca245b\n address: 9iFic39ctD5pngbHWX3WFipEJDRAZ2wyHircd8pCqGycHd7VrxX\n assets: []\n spentTransactionId: null\n mainChain: true\n}"
2 = {OutputInfo@28550} "class OutputInfo {\n boxId: 10fd280e5d00a4735a0bfedbd5608278b929a19246316b56b5028b8fcb37cf75\n transactionId: d8e68b2b84aede4fd00ac21d60388bdb3539f2c1d554acf258a4f51d9c6d25ef\n blockId: 84d0dd691d73fe7e731e2d8449307617cd9a9a9b8c827697e2a7ce54635cbf8c\n value: 10000000000\n index: 0\n creationHeight: 597741\n settlementHeight: 597743\n ergoTree: 0008cd03ebf2124bc8da317bf0152e416594df3662a5d6e947d75920c228b368bbca245b\n address: 9iFic39ctD5pngbHWX3WFipEJDRAZ2wyHircd8pCqGycHd7VrxX\n assets: []\n spentTransactionId: null\n mainChain: true\n}"
3 = {OutputInfo@28551} "class OutputInfo {\n boxId: 12acc1cfcc6b9c22170fbeb13852dc866add291b92eed67e37cbda2a8fa29796\n transactionId: 58d04ade811523d952a0bfc35fe77d34f9915b6c2e0fe430a960d40a9d2ee6be\n blockId: 84d0dd691d73fe7e731e2d8449307617cd9a9a9b8c827697e2a7ce54635cbf8c\n value: 10000000000\n index: 0\n creationHeight: 597741\n settlementHeight: 597743\n ergoTree: 0008cd03ebf2124bc8da317bf0152e416594df3662a5d6e947d75920c228b368bbca245b\n address: 9iFic39ctD5pngbHWX3WFipEJDRAZ2wyHircd8pCqGycHd7VrxX\n assets: []\n spentTransactionId: null\n mainChain: true\n}"
4 = {OutputInfo@28552} "class OutputInfo {\n boxId: 6ea1f6c064dd8ad6a01dff9378a71ec0e8b4194e4d7d5f8e04b4150d27f14d85\n transactionId: 513d31a418f8a24df2bae0b9a10aed859dc854c503e36f9e9660cc61b7ad86c9\n blockId: 84d0dd691d73fe7e731e2d8449307617cd9a9a9b8c827697e2a7ce54635cbf8c\n value: 10000000000\n index: 0\n creationHeight: 597741\n settlementHeight: 597743\n ergoTree: 0008cd03ebf2124bc8da317bf0152e416594df3662a5d6e947d75920c228b368bbca245b\n address: 9iFic39ctD5pngbHWX3WFipEJDRAZ2wyHircd8pCqGycHd7VrxX\n assets: []\n spentTransactionId: null\n mainChain: true\n}"
We can work around this behaviour by doing our own check in BoxOperations.loadTop
and only add boxes that weren't already added. But doing so, we will miss some other boxes. Best would be a check of Explorer's code and sorting by a second column.
API used is transactions/boxes/byAddress/unspent/{id}
https://api.ergoplatform.com/api/v1/boxes/unspent/byAddress/9iFic39ctD5pngbHWX3WFipEJDRAZ2wyHircd8pCqGycHd7VrxX?offset=0&limit=20
I am trying to use the new WalletApi
to get some boxes from my wallet.
I created the wallet service like this
// conf is of type ErgoToolConfig
val apiClient = new ApiClient(conf.getNode.getNodeApi.getApiUrl, "ApiKeyAuth", conf.getNode.getNodeApi.getApiKey)
val walletService = apiClient.createService(classOf[WalletApi])
Then I'm using it like this to get some amount of ERG from my wallet
val getWalletBoxesRequest = new BoxesRequestHolder().targetBalance(Parameters.OneErg)
val response = walletService.walletBoxesCollect(getWalletBoxesRequest).execute()
But that call throws the following error:
Exception in thread "main" java.lang.IllegalArgumentException: Non-body HTTP method cannot contain @Body.
After a little digging, I found that WalletApi.walletBoxesCollect
queries /wallet/boxes/collect
in the node's API, which is a POST endpoint.
However, I noticed that the appkit method that queries that endpoint has a @GET
annotation for it
It seemed like the fix was as simple as changing the annotation from @GET
to @POST
, so I tried it locally. For simplicity's sake, I created a Java interface in my codebase,
public interface NewWalletApi {
@Headers({
"Content-Type:application/json"
})
@POST("wallet/boxes/collect")
Call<List<WalletBox>> walletBoxesCollect(
@retrofit2.http.Body BoxesRequestHolder body
);
}
updated the wallet service creation to use the interface,
val walletService = apiClient.createService(classOf[NewWalletApi])
and tried executing a request again.
val getWalletBoxesRequest = new BoxesRequestHolder().targetBalance(Parameters.OneErg)
val response = walletService.walletBoxesCollect(getWalletBoxesRequest).execute()
This time, I just got a Bad Request
back from the node, no further details.
So I went to the node's Swagger page to inspect that endpoint further (POST /wallet/boxes/collect
), and soon learned some things about the expected request body.
targetAssets
is required and cannot be null
.targetAssets
is shown as a List of Lists (the BoxesRequestHolder
Java model reflects this).I tried out a few combinations
// empty list
{
"targetAssets": []
...
}
// empty list with empty list
{
"targetAssets": [[]]
...
}
// list of lists with "dummy" values
{
"targetAssets": [["", 0]]
...
}
None worked. Until finally I tried this, and it worked!
{
// empty object/map
"targetAssets": {}
...
}
So, it looks like
targetAssets
cannot be null
but it can be empty.targetAssets
needs to be an object/map (where each key/value pair represent asset_id/amount), NOT a list of lists (array of arrays).So, I tried updating the targetAssets
property in the BoxesRequestHolder
model to a HashMap<String, Long>
, executed another request from my code using appkit, but then I got this error:
Note: to facilitate and speed up local testing of this, I created a new model NewBoxesRequestHolder
, updated targetAssets
as needed, and used it to build a request body instead of BoxesRequestHolder
public class NewBoxesRequestHolder {
@SerializedName("targetAssets")
private java.util.HashMap<String, Long> targetAssets = new HashMap<>();
@SerializedName("targetBalance")
private Long targetBalance = null;
public NewBoxesRequestHolder targetAssets(java.util.HashMap<String, Long> targetAssets) {
this.targetAssets = targetAssets;
return this;
}
public NewBoxesRequestHolder targetBalance(Long targetBalance) {
this.targetBalance = targetBalance;
return this;
}
public java.util.HashMap<String, Long> getTargetAssets() {
return targetAssets;
}
public void setTargetAssets(java.util.HashMap<String, Long> targetAssets) {
this.targetAssets = targetAssets;
}
public Long getTargetBalance() {
return targetBalance;
}
public void setTargetBalance(Long targetBalance) {
this.targetBalance = targetBalance;
}
}
Exception in thread "main" java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $ at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:350) at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:80) at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61) at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:39) at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27) at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:225) at retrofit2.OkHttpCall.execute(OkHttpCall.java:188)
While I've figrued out how to send a valid request to POST /wallet/boxes/collect
via the node's Swagger page, I have not yet managed to get it to work via appkit.
I'm still into it but this is as far as I've made it. If anyone has any useful insights into how to potentially resolve this issue, they'd be greatly appreciated!
When we create a transaction with some inputs having context variables set via withContextVar
, the following behaviour is observed:
tx.toJson
does not include context variables.The above indicates that while the context variables are used in signing, they may not be included in the final transaction.
In case setting prettyPrint to true, "ergoTree" parameter of boxes in the transaction has a problem like the below example:
{ "ergoTree": "ErgoTree(0,WrappedArray(),Right(ConstantNode(SigmaProp(ProveDlog(ECPoint(4a186c,125e7c,...))),SSigmaProp)),80,[B@73d60e76)", }
Current method Address.fromMnemonic is not EIP-3 compliant.
A new method Address.fromMnemonicEip3 needs to be created to support generation of EIP3 compliant first address.
Currently, it is not possible to generate transactions with fewer tokens in outputs.
Now explorerApi V1
just return 20
unspent boxes, for getting more unspent box should be used query param limit
but in scala side, there is not this feature.
Other APIs may have this limitation from Explorer.
Currently, UnsignedTransactionBuilderImpl
uses DefaultBoxSelector
internally. The implementation has some major and some minor drawbacks that should be solved by changing the implementation:
DefaultBoxSelector
is part of ergo-wallet dependency, bugfixes/enhancements/behaviour changes are hard to doThe second issue is especially restricting client code. For example, building the following transaction fails:
=> Fails because second input is not needed. This is true, but when client code wants to build a transaction in this way, this should be accepted by the transaction builder and the unnecessary amount shoud be added to the changebox.
To add up to the confusion, it is even possible to add unnecessary boxes to the input list, some are accepted:
=> accepted
That means that every client code must all DefaultBoxSelector
manually on an input box list to make sure the input boxes list satisfies the needs. This is done in appkit's BoxOperations loadTop
method as well, which calls BoxSelectorsJavaHelpers.selectBoxes
which is using DefaultBoxSelector
internally, and must be done by client code on every transaction build.
Getting error Cost of transaction ErgoLikeTransactionTemplate( ... ) exceeds limit 5062531
In appkit test node responses, I've set the maxBlockCost
to be 5062531 as per the current mainnet (ref @kushti )
Full error here https://gist.github.com/scalahub/67a8293010428ea2bbfa2085690116fc
Here is the test that generates the error
https://github.com/scalahub/AppkitIssue/blob/main/src/test/scala/AppkitIssue.scala
The same transaction (plus few additional inputs) when created via the ergo node works fine:
https://explorer.ergoplatform.com/en/transactions/eab3020a702165b1328f720df99cee051b5c282b52fde563c291f97113bad1f3
We can verify that the addresses in the above tx and the test (AppkitIssue.scala) are the same
For reference, the scripts of the addresses correspond the code here:
ErgoScript: https://github.com/scalahub/OraclePool/blob/main/src/main/scala/oraclepool/v1a/Contracts.scala
Addresses: https://github.com/scalahub/OraclePool/blob/main/src/test/scala/oraclepool/v1a/AddressSpec.scala
One of the most important goals of the mixer is anonymity, so it is important to be able to send requests through a proxy (Tor, ...).
This feature is currently implemented in the custom version of Appkit within the mixer project, and I'm trying to transfer these details to the official version of Appkit.
The following changes are made to the custom version:
RestApiErgoClient
ExplorerApiClient
The following error is thrown on older Android devices (< 8.0) when Address.fromMnemonic is called:
java.security.NoSuchAlgorithmException: SecretKeyFactory PBKDF2WithHmacSHA512 implementation not found
at org.apache.harmony.security.fortress.Engine.notFound(Engine.java:190)
at org.apache.harmony.security.fortress.Engine.getInstance(Engine.java:139)
at javax.crypto.SecretKeyFactory.getInstance(SecretKeyFactory.java:108)
at org.ergoplatform.wallet.mnemonic.Mnemonic$.toSeed(Mnemonic.scala:77)
at org.ergoplatform.appkit.JavaHelpers$.seedToMasterKey(JavaHelpers.scala:354)
at org.ergoplatform.appkit.JavaHelpers.seedToMasterKey(JavaHelpers.scala)
at org.ergoplatform.appkit.Address.fromMnemonic(Address.java:135)
Algorithm is not included in Java 7. As you already ship with Bouncy castle, it should be easy to switch to its implementation that is available on all language levels. See here: https://stackoverflow.com/a/22621191/7487013
When creating custom output boxes using OutBoxBuilder, we should be able to specify the creationHeight.
The task is to open access the new signing functions of Sigma v4.0.1 added in ScorexFoundation/sigmastate-interpreter#685
From discord.
scalahub โ 05/10/2021
Can you modify appkit to accept zero fee when creating tx.. There is a situation where I need to add fee output manually and not via appkit.
Basically another method that allows user to specify the exact tx without appkit adding anything extra.
Following https://discord.com/channels/668903786361651200/1073484137131950091/1097795535886372926
The node will reject it anyway.
Ideally, box value should be more than min value per byte * box size, but some compromises could be made, like in https://github.com/ergoplatform/sigma-rust/blob/e7bebc6ef7c7c6f5c05e4ae72ccd2e5690100284/ergotree-ir/src/chain/ergo_box/box_value.rs#L31-L47
According to the discussion in discord, I think it is better if explorerUrl to be optional so that Appkit method can work with Node and Explorer switch case defaultMainnetExplorerUrl or defaultTestnetExplorerUrl
as long as specific Explorer is actually not needed.
Appkit can be used in many execution environment configurations.
This issue is to specify which of them exactly and how to use Appkit there.
When calling BlockChainContext.sendTransaction
, the transaction id is returned with quotes. It would be better if the txId is returned without quotes.
Analogous to ergoplatform/sigma-rust#611, we need appkit to support conversion between json to ErgoValue and vice versa. Or at the very least, support types introduced in EIP-24 and 34.
The motivation behind this is explained here by @aslesarenko: ergoplatform/eips#69 (comment)
I've completed the basic startup configuration and verified that my GraalVM is successfully installed. I'm using GraalVM CE 19.3.6, which has the openjdk 11.0.11. I'm using the current appkit 2.12-04.0.5 file which I installed from the published version. I'm on Ubuntu 20.0.4.
I have a simple script that I've cut down from FreezeCoin.js:
const Integer = Java.type("java.lang.Integer")
const Long = Java.type("java.lang.Long")
const Address = Java.type("org.ergoplatform.appkit.Address")
const RestApiErgoClient = Java.type("org.ergoplatform.appkit.RestApiErgoClient")
const ErgoClientException = Java.type("org.ergoplatform.appkit.ErgoClientException")
const ConstantsBuilder = Java.type("org.ergoplatform.appkit.ConstantsBuilder")
const ErgoToolConfig = Java.type("org.ergoplatform.appkit.config.ErgoToolConfig")
const Parameters = Java.type("org.ergoplatform.appkit.Parameters")
const conf = ErgoToolConfig.load("freeze_coin_config.json");
When I run this with the command line: js --jvm --vm.cp=target/ergo-appkit_2.12-4.0.5.jar examples/test.js
I get the following error:
java.lang.NoClassDefFoundError: com/google/gson/GsonBuilder at org.ergoplatform.appkit.config.ErgoToolConfig.load(ErgoToolConfig.java:53) at org.ergoplatform.appkit.config.ErgoToolConfig.load(ErgoToolConfig.java:67) at <js> :program(examples/test.js:10:557-590)
My config file is present in the directory. Any suggestions on how to resolve?
As in the title, I now have the signature data obtained using the sdk offline signature, but how to deserialize the signature data to get the corresponding amount, address and other transaction information.
ErgoValue contractor to be able to put Box instances in registers.
I tried to execute some methods in AddressesApi
without success:
addressesIdGet
: Failed because FullAddressTransactions
declares integers, but longs are comingaddressesIdTransactionsGet
: Failed because it should return List<Transaction>
but api call wraps the listLooks like the wrappers need to be refreshed.
As a suggestion, maybe this should be extracted to an own lib. This way, it could get updated or changed without the need to update appkit itself.
.isCovered only checks that the required erg amount is covered, ignores the required tokens to spend.
SecretStorage
/JsonSecretStorage
are convinience classes operating on Files. It would be good if we have a way to persist the encrypted ExtendedSecretKey
to other places than File
. On Android, it is better to use a sqlite db for this, so a method returning the serialized end encrypted JsonSecretStorage as byte[]
or String
would be handy, and we would need of course the counterpart to create a SecretStorage from byte[]
or String
.
Until we have that, what would be the recommended work around to save the secrets to a db field? Easiest option would be to save the encrypted mnemonic and regenerate the secrets every time when needed, however, this does not feel solid.
Update: Found out secretstorage saves mnemonic anyway, so I do the same.
Then reuse it from there.
If provided inputs and outputs happen to have the same amount of erg then tx builder fails because apparently it can not create change box.
This condition is not True, this condition is true in not mode
Should be based on Sigma version currently used for v5.0 Ergo node release candidate.
I get a signing error if the tx cost exceeds this could be confusing for unfamiliar users.
Existing implementation will select boxes with tokens, which will lead to invalid transactions.
BoxOperations.selectTop method should filter out the boxes which contain tokens
Currently, AppKit supports primitive secrets for DHT, but only ExtendedSecretKey for Dlog.
Please note that in recent versions of ergo-wallet, SecretKey hierarchy was introduced, with support for both primitive and BIP-32 secrets. Thus AppkitProvingInterpreter constructor input can be simplified to just (JList[SecretKey)] (better to save old one also for the sake of backwards compatibility for sure)
Currently the following places are hard coded:
val BaseDocUrl = "https://ergoplatform.github.io/ergo-tool/api"
Usage Syntax:\tergo-tool $name ${cmdParamSyntax}
Thus:
def baseDocUrl: String
in CmdDescriptordef launcher: String
method to use in Usage Syntax above.A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.