ava-labs / avalanchejs Goto Github PK
View Code? Open in Web Editor NEWThe Avalanche Platform JavaScript Library
License: Other
The Avalanche Platform JavaScript Library
License: Other
Is it currently possible to compute the transaction ID before calling issueTx
? I am working on an integration and currently have to generate UUIDs to keep track of the transactions because I don't see any way of getting a transaction ID before broadcasting it.
On the AVM API there is a function: avm.getAssetDescription
. The parameters are: assetID=string
. The reply is: name=string
and symbol=string
The following path and file names are now hardcoded in gecko
cpuProfileFile
memProfileFile
lockProfileFile
They have also been removed from their respective RPC calls.
We need to remove the following
filename
from startCPUProfilerfilename
from memoryProfilefilename
from lockProfileWhen I updated the avalanche
to 2.6.2
in the code sandbox project. it throws the error
Could not find module in path: 'readable-stream/errors' relative to '/node_modules/readable-stream/lib/internal/streams/state.js'
It seems like the [email protected]
package is missing some dependencies.
In the package.json of avalanche (https://github.com/ava-labs/avalanche.js/blob/ab1d22ad5298c4e04fd17abad65316ef21e0f102/package.json#L6) the browser field refers to dist/lib/avalanche.js, but upon checking I don't think that folder exists. Only the dist folder exists, but it doesn't contain a lib folder: https://unpkg.com/browse/[email protected]/dist/.
This is causing the issue(#89) and some env will not work well.
Can anyone help to fix this?
I just spent a bit of time looking through an unsigned transaction generated by avalanche.js to try to understand what the messages look like. I managed to work my way through all of it, and the only thing that has me scratching my head is the memo.
The conclusion I've come to is that maybe the memo gets included into the payload wrong. It's a bit of a long writeup here, but maybe it'll be of some use.
I used the code from the Figment pathway to generate the transaction:
const unsignedTx = await chain.buildBaseTx(
utxos, // unspent outputs
new avalanche.BN(amount), // transaction amount formatted as a BigNumber
assetID, // AVAX asset
[receiver], // addresses to send the funds
[address], // addresses being used to send the funds from the UTXOs provided
[address], // addresses that can spend the change remaining from the spent UTXOs
binTools.stringToBuffer("Figment Pathway") // memo, totally optional
)
// I dumped the payload like this
console.log(unsignedTx.toBuffer().toString('hex')
Here's the raw payload that was generated:
00000000000000000005ab68eb1ee142a05cfe768c36e11f0b596db5a3c6c77aabe665dad9e638ca94f7000000023d9bdac0ed1d761330cf680efdeb1a42159eb387d6d2950c96f7d28f61bbe2aa000000070000000002faf080000000000000000000000001000000019285d84bf6b302bca03a6876a5281a84deab3c613d9bdac0ed1d761330cf680efdeb1a42159eb387d6d2950c96f7d28f61bbe2aa00000007000000002f71ff0000000000000000000000000100000001f05d865afb866fde3e3c36a9c6a902e5f481d49b000000010f2b6cf475e45d7277eee2e2a61dd1799188ee5f57b79e8a4c993b77d4c0bbd2000000013d9bdac0ed1d761330cf680efdeb1a42159eb387d6d2950c96f7d28f61bbe2aa0000000500000000327c31c0000000010000000000000011000f4669676d656e742050617468776179
Here are the memo bytes that were generated:
# memo - 4 bytes before the memo, I think this is meant to be the length
00 00 00 11
# This is the memo, but if you convert `Figment Pathway` direct to hex it starts at 0x46.
# 0x00 0x0f that is generated at the beginning probably corresponds to the actual length of the memo?
00 0f 46 69 67 6d 65 6e
74 20 50 61 74 68 77 61
79
So my understanding is that the expected memo part of the payload is:
# 4 bytes representing memo length of 15
00 00 00 0f
# the actual bytes for the memo
46 69 67 6d 65 6e 74 20
50 61 74 68 77 61 79
Maybe I'm missing something, this is my first dive in here.
Here's my full analysis of the payload:
# this is the codecID. my understanding is this shouldn't be part of the unsigned tx, but maybe it's just generated because it's always 00 00 right now, and then to sign we sign from offset 2.
00 00
# typeID
00 00 00 00
# network ID - 5 for fuji testnet
00 00 00 05
# blockchainID
ab 68 eb 1e e1 42 a0 5c
fe 76 8c 36 e1 1f 0b 59
6d b5 a3 c6 c7 7a ab e6
65 da d9 e6 38 ca 94 f7
# number of outputs
00 00 00 02
# output #1 - asset ID
3d 9b da c0 ed 1d 76 13
30 cf 68 0e fd eb 1a 42
15 9e b3 87 d6 d2 95 0c
96 f7 d2 8f 61 bb e2 aa
# output #1 - secp256k1 typeID
00 00 00 07
# output #1 - amount (50000000)
00 00 00 00 02 fa f0 80
# output #1 - locktime (0)
00 00 00 00 00 00 00 00
# output #1 - threshold (number of sigs required to spend, 1)
00 00 00 01
# output #1 - number of addresses that can spend this (1)
00 00 00 01
# output #1 - first (and only) address that can spend this output; this probably
# corresponds to the "receiver" somehow, but I don't know how.
# this should be 20 bytes
92 85 d8 4b f6 b3 02 bc
a0 3a 68 76 a5 28 1a 84
de ab 3c 61
# output #2 - asset ID
3d 9b da c0 ed 1d 76 13
30 cf 68 0e fd eb 1a 42
15 9e b3 87 d6 d2 95 0c
96 f7 d2 8f 61 bb e2 aa
# output #2 - secp256k1 typeID
00 00 00 07
# output #2 - amount (796000000), change - adds up almost to the output amount, less 1000, which I guess is the transaction fee
00 00 00 00 2f 71 ff 00
# output #2 - locktime (0)
00 00 00 00 00 00 00 00
# output #2 - threshold (number of sigs required to spend, 1)
00 00 00 01
# output #2 - number of addresses that can spend this (1)
00 00 00 01
# output #2 - first (and only) address that can spend this (should be same as input address)
f0 5d 86 5a fb 86 6f de
3e 3c 36 a9 c6 a9 02 e5
f4 81 d4 9b
# number of inputs
00 00 00 01
# input #1 - tx id
0f 2b 6c f4 75 e4 5d 72
77 ee e2 e2 a6 1d d1 79
91 88 ee 5f 57 b7 9e 8a
4c 99 3b 77 d4 c0 bb d2
# input #1 - utxo index (is this 1- or 0- indexed?)
00 00 00 01
# input #1 - asset id
3d 9b da c0 ed 1d 76 13
30 cf 68 0e fd eb 1a 42
15 9e b3 87 d6 d2 95 0c
96 f7 d2 8f 61 bb e2 aa
# input #1 - type ID (secp256k1 input)
00 00 00 05
# input #1 - amount (847000000, I guess maybe this is what I got from the faucet?)
00 00 00 00 32 7c 31 c0
# input #1 - number of address indexes (1)
00 00 00 01
# input #1 - address index #1
00 00 00 00
# memo - 4 bytes before the memo
00 00 00 11
# memo contents?
00 0f 46 69 67 6d 65 6e
74 20 50 61 74 68 77 61
79
I need to be specific when checking for "undefined" type. Example: a numeric index is false if its value is 0, but what I really worry about is "undefined".
I just discovered that the client has an RPC call mintNFT
and createNFTAsset
. While AJS's support for these functionalities is more robust, we need to stay in-line with the node RPCs. We need to go through the RPCs and make sure they're available in AJS for all VMs.
This thing is over 1MB in size once bundled. That's mostly due to the crypto helper libraries we're using. Need to figure out how to make that smaller and keep the functionality.
Update defaults before Denali.
Once the promise of avm.getAllBalances resolves, if result is null/undefined this line causes an error.
TypeError: Cannot convert undefined or null to object
I also noticed that the Metrics API is not present in Slopes . (https://docs.ava.network/v1.0/en/api/metrics/)
Gecko produce the Metrics as a 'Prometheus compatible metrics' .
I propose that we create an endpoint that return the metrics either "raw" (prometheus style) or as a JSON (I'm using this in Snowboard : https://www.npmjs.com/package/parse-prometheus-text-format) .
What do you think ?
How can I get the address on an input w/o querying again with txhash/vout?
Is it possible to get the address from signature somehow?
I am getting the details of a tx with AvalancheJs (GetTxByID) and I can see the input, its txhash etc. How about address, is there a way to get that from information inside the tx?
The README is out of date. Filing issue to remember to update it.
avalanchejs/src/apis/platformvm/api.ts
Line 896 in 4d54fb1
The returned type for numFetched should be a number but the api result is a string.
avalanchejs/src/apis/platformvm/api.ts
Line 936 in 4d54fb1
Maybe parse the response and return an int?
platform.sign
has been removed as a Gecko's RPC call so sign can be removed from platformvm
Currently, CreateAsset.AssetID() reports the AssetID as 0. However, the AssetID of the new asset should be the TxID of the transaction the output was issued in.
When I am using this code:
"let utxos = xchain.getUTXOs(check); "
It is giving me this Error: connect ECONNREFUSED 127.0.0.1:9650
similar to
avalanchejs/src/apis/avm/api.ts
Line 240 in 629a4b7
I have an issue with the Metrics API .
In short, it works well in a nodeJS app, but not when used in a Vue app (response is always empty) . I believe it is because of the responseType
set in AxiosRequestConfig
. It is set to json
all the time, but in fact the /ext/metrics
api is returning :
I guess my browsers (tested in chrome and firefox) does not like this and somehow 'protect' me by blocking the response .
I did a small experiment, modifying the AxiosRequestConfig in types.ts (replaced reponseType : json
by :
const axConf:AxiosRequestConfig = {
baseURL: `${this.core.getProtocol()}://${this.core.getIP()}:${this.core.getPort()}`,
responseType: 'text',
};
in Avalanche , ran it locally and tadaaaa I got a response with all metrics .
While Avalanche Node continues to bootstrap=true
node killed because of unknown reasons (the logs in .txt files)
Avalanche version 1.0.1 and running on Ubuntu 18.04 lts on windows 10 pro
Hi all,
I have been looking through a bunch of docs and source code and would appreciate some input on the following topics/improvements to the JS package:
/my/fancy/prefix
, example: /my/fancy/prefix/ext/info
. From the docs/code it seems like its possible to configure for specific clients, like AVM, but not possible for the new avalanche.Avalanche
call. Maybe add another top level constructor like new avalanche.AvalancheFromURL('https://myhost.com/api/prefix
) so its easier to initialize.setAuthToken
call which sets internal bearer authentication but for other general purpose headers. Example: avalancheClient.setHeader('X-Api-Auth', 'foobar')
. This will allow access to nodes running behind API gateways, etc.Thanks!
Add the possibility to deserialize PVM block's bytes coming out of the IPC socket .
I was working on my rust pet project and while working on the PChain deserialization (with bytes coming out of the IPC socket) I noticed that I could not use avalancheJs PVM Tx.fromBuffer() . It would always fail .
It is because avalanchejs only deserialize bytes of a transaction , but on the pchain the bytes coming out of the socket are composed like this :
So someone trying to use avalanchejs with the bytes coming out of the socket would only see errors .
It was rather confusing for me at first because even in the documentation there are no mentions of the structure of a PVM Block ( Added few issues in the repo as well) , and I had to dig in avalanchego code in order to understand how to deserialize it .
Follow same approach as for the PVM Tx .
Having issue trying to get AVM importAVA to work. exportAVA was successful and the resulting transaction was accepted. But if I use importAVA with the same address, I get error "problem parsing to address: invalid address". If I prefix the address with "X-" (as mentioned in the docs) error "problem issuing transaction: no import inputs".
Unless am blind, Slopes does not implement those 2 calls for the Platform API :
platform.exportKey
platform.importKey
PR #38
I noticed that buildImportTx
method actually gets the utxos from the node. Isn't this redundant since utxoset
is already given as a parameter?
avalanchejs/src/apis/avm/api.ts
Line 880 in 70413e8
Hi everyone,
How do I get the asset id from an utxo output (from avm.getUTXOs) (or is it possible??) with avalanchejs or else?
thank you
I stumbled upon an annoying bug .
When calling exportAVA
from the PlatformAPI , you need to provide 3 params :
to
nonce
amount
, which need to be passed as a BN in Slopes .My issue is with this amount
.
I'm doing the following :
const txId = await api.Platform().exportAVA(new BN(data.amount), data.to.substring(2,data.to.length), data.nonce);
where data.amount
is equal to 10 .
Am expecting to see in my network tab a call to platform.exportAVA with 10 in request param for amount but ... I see :
I did put some logs in Slopes to see what the heck is happening.
It seems that the params that are passed to the callMethod
are at one point converted to a string via JSON.stringify(rpc)
, and looks like it transform the BN that have a value of '10' to this '0a' value .
See :
export class JRPCAPI extends APIBase {
protected jrpcVersion:string = "2.0";
protected rpcid = 1;
callMethod = async (method:string, params?:Array<object> | object, baseurl?:string):Promise<RequestResponseData> => {
...
...
return this.core.post(ep, {}, JSON.stringify(rpc), headers,
...
This JSON.stringify(rpc) give something like :
{"id":2,"method":"platform.exportAVA","params":{"to":"AwnMtRHWF7rYZm8x7VkRdTLnZUoboitRe","amount":"0a","payerNonce":3},"jsonrpc":"2.0"}
I would expect 10
in place of 0a
there : )
Am I missing something there ?
In the index.ts file of slopes, be sure to update the network defaults before launch of mainnet.
Update the network defaults to have correct chainid and networkid
The locktime
which is being passed in to buildCreateAssetTx isn't being handled.
We can pass locktime
to utxoset. buildCreateAssetTx
like we do with utxoset.buildCreateNFTAssetTx
below in buildCreateNFTAssetTx
Hi guys,
I'm using slopes (version 1.4.3) to interact with the network, there is an issue with the platform.ListValidators
method.
This is the instruction in my code:
let vals = await platform.listValidators();
and this is the output I get:
Error: Error: Error returned: {"jsonrpc":"2.0","error":{"code":-32000,"message":"rpc: can't find method \"platform.ListValidators\"","data":null},"id":1}
at /Users/francaviglia/ava-examples/node_modules/slopes/typings/src/utils/types.js:111:31
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async test (/Users/francaviglia/ava-examples/for-cycle/for-cycle.js:239:12)
Errors thrown by avalanche.js should be typed so users can handle them differently. There should be a dedicated Error subclass with code and message attributes returned by the RPC server. This way as the library user I can look for specific error codes and handle them differently.
Hi,
I'm using avalanche.js to stream data from ipc socket and got this exception
/app/node_modules/avalanche/typings/src/apis/avm/tx.js:502
throw new Error(`Error - SelectTxClass: unknown txtype ${txtype}`);
^
Error: Error - SelectTxClass: unknown txtype 4
at Object.exports.SelectTxClass (/app/node_modules/avalanche/typings/src/apis/avm/tx.js:502:11)
at UnsignedTx.fromBuffer (/app/node_modules/avalanche/typings/src/apis/avm/tx.js:368:36)
at Tx.fromBuffer (/app/node_modules/avalanche/typings/src/apis/avm/tx.js:422:34)
while i will try..catch
as i probably don't need it for now, i was wondering what are Type 4
transactions and if it will supported by avalanche.js ?
Bests
The APIs have changed. I need to find out what changes need to be made to slopes, make those changes, and test them.
Currently in the documentation when issuing a transaction, it is suggested to collect UTXOs by let utxos = await avm.getUTXOs(myAddresses);
. Although this works, it is not ideal to send all UTXOs of different assets, to a single transaction of a single asset.
It would be nice to have a function such as avm.getUTXOsByAssetId(assetId)
so that the user gets only the relevant UTXOs for the asset being transferred. An even better way would be avm.getUTXOsByAssetId(assetId, amount)
so that only the minimum required number of UTXOs are sent with the transaction.
Hello,
I noticed that /ext/health
endpoint is missing in Slopes .
I'm using it for my small dashboard project and I would like to migrate all my calls via Slopes :)
Should I start a PR on this or was it left out intentionally ?
By the way this endpoint is not documented, I'll open an issue over there as well :)
Edit : Add an example of call / response :
curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"health.getLiveness"
}' -H 'content-type:application/json; http://localhost:9650/ext/health'
{
"jsonrpc": "2.0",
"result": {
"checks": {
"network.validators.heartbeat": {
"message": {
"heartbeat": 1590918823
},
"timestamp": "2020-05-31T09:54:36.733208646Z",
"duration": 7434,
"contiguousFailures": 0,
"timeOfFirstFailure": null
}
},
"healthy": true
},
"id": 1
}
I noticed that there is a typo in the Keystore/api.ts
, importUser
function.
It leads to the creation of a user with "" as username in Gecko .
Here is the typo (PR open btw)
importUser = async (username:string, user:string, password:string):Promise<boolean> => {
let params = {
"usermame": username, <== HERE
"user": user,
"password": password
};
return this.callMethod("keystore.importUser", params).then((response:RequestResponseData) => {
return response.data["result"]["success"];
});
}
I'll open an issue (and why not a PR, I want to practice Go :p ) in Gecko, because we should not be able to import an user with an empty username. (There is a specific check in the createUser flow that is missing in the importUser flow)
This was added recently to Gecko and is not yet in Slopes.
curl -X POST --data '{
"jsonrpc": "2.0",
"method": "admin.getNetworkName",
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/admin
{"jsonrpc":"2.0","result":{"networkName":"denali"},"id":1}
It was added in Gecko 0.5.4 (0.5.5) , and need to be added in Slopes !
Presently there's many classes in single, large files. We need to split those out into individual files for readability as right now it takes a lot of scrolling around to find what you want.
I'm attaching some examples of minting txs from mainnet that fail to parse with two different errors:
txid: 2B7efoZDxQ9NRpdPixp2NQe2oYGeLQ6yd27C7UXvb2gfb4soJf
error: Error: Error - NBytes.fromBuffer: Error: Buffer length must be 20 bytes. Only have 10 remaining in buffer.
bytes:
11111eqtQvbRXgLu2Ggh1sfXyRxKRFwxiSC283i1LX8fS8uyHtcbbWDxASbh9ktwpg45Hnb5DXR8rHHp9opSTuPZZ1yHpPXP2dm6WDJtjJsmjcSCptEsRemh5jXS8sGNNduHQUj7RP6JUd3kuEpeFFA2jHpuHEDbFddCzhp5MSfTDhWpXXXQnQyAioiPTvoa2Vq53w6KPaTQqkiff11pCV4oR1TZn256p5WQowww2JtUrYm13pKHobfo9AQ3qybD3fzXXQxaDFTXXx4hCAzG4vNzZZSUU2wAnAVpmipxPXGc83pa4rvwBndUFY3JeBcr76jqiQyQoQA3ndM7mRrBUVn5ZTFTDjtT5iMgUCmoxVVDtnY9J6sNi8AZkddnrMsysipGgb6Gmngxc4RrAXvEYEW1uPcYW5DkA2QYuEkgA8RLUghCyLycmnQQeZaL8hAiBbK738nWmyDCy2RWxUuRuCHK1MxGPJQ6vgUUWM2xDb7BcfFgbNu2i3wD6McmKvT5BY4kZa16QyZgA48cJuTeSjZ2fGwDpUjRwGaNEVyEBwdUg9zdq675reoc5nNPd3rycQ9SYqzPdXGzt2vCbUn7L245J6KHGQHG1Pt5otMZr9AgqbWQHonioEKx4UBP1Jkb39wbbBxR2jmLkLuKro6YsWdJvs7JPPbToKnGrPTdHmEJA5L7iB8vLCJyWyuMiD1F7pDXZDVonFP2rss8JLBpcWoAZtXuns85kW5G
txid: 82jDmJsjRtr2PWVVHPP35jmnUfrqQV2rQodBN6njxxdrEAqQt
error: Error - NBytes.fromBuffer: Error: Buffer length must be 20 bytes. Only have 10 remaining in buffer.
bytes:
11111eqtQvbRXgLu2Ggh1sfXyRxKRFwxiSC283i1LX8fS8uyHtcbbWDxASbh9ktwpg45Hnb5DXR8rHHp9opSTuPZZ1yHpPXP2dm6WDJtjJsmjcSCptEsWxuAH1rDiZ6hY3ZQ5AcqJ4Gp2AntfGLUYjtTMNL6icunDzj5pj4dfTD8kW8or3Hr6i9iUu7UadMMATxH59m3ctx6xqY4WToWFLRjn3QKAHA8anbf3o2FDHYLQpxNNpGuWiZB5oiG61KraevHr711zS2Yn6ZNNvnQh6i46f8pNZQRjZCoxGqd9vmaCf2kW1EwYxicfG2Eqwgf95gJaGWgsPV5zAEyjNX7ohLaTMsAdd35GCSavvRT9XYRZj88By4Qmn5o3hTLsSyhjYL8t8E2KyC8uhX2DQ3Vwg2sV7YKyktBCtxmFbyAbWBMdik4fU5gi2XUhiDjQDs1QWGiK6Ao7skknfEEyEvoYEjHYZBjAnXWk6bzTtruEtSPbZMrvKLS7ppKwW9NVthnj39cwBFoyzTEnYCXU68mYYTGatHMVdqWP3VBqq6nALJJ4fFHZ9wUNeE4RQDoT6dRVGTTz7vXmjSsWPgvGagBkjQLpJb9HsiygLsCdirpbtdqdxqgySkMNXWcNKwGhJdG3MEnXDr2QDEo444X1yjebctS3ck6sbSWwrYGZ7Nan8nqTLjkRiCCx1Gugvain72ooe8drXkekS1vqZPsQbX3nFuFv5qG2yRoEZzC
txid : 2fNEUGLxzs9dJEkwqXEUVKYXy34m82NK3pZuztQpKs5HdrQR5J
error: Error: Error - SelectCredentialClass: unknown credid 0
bytes:
11111eqtQvbRXgLu2Ggh1sfXyRxKRFwxiSC283i1LX8fS8uyHtcbbWDxASbh9ktwpg45Hnb5DXR8rHHp9opSTuPZZ1yHpPXP2dm6WDJtjJsmjcSCptEscH2dUJB1JEw2hTDWjrWZAjTKZiY2RHrJrEcsySqJA1byCMpxekKByTkpHJd8TjGLqEFsf5qmY8LxmAhUi16zoJhXAbLRHgiwTQ9vTxAdL4SUpmTjjnKWdF5Jh59CNfF61KZGzWy7ATxjGLL9RGzy1rQrxLvnL8KxFx8HyShNYrPdoLsRkwVA6htAfN9DaLen2WyGpjCCfMShzAhfqD1bdKshVNncviLEXisCds2dnjLspeTudhBocqA7fsb5bVPHD15Lrvdk3RZsRBBoiFi1MkFxsRFWRDwej4izcj7UUoXE3vUJkev7hgdPoALUDjfgsyTTHei3nSrFbVYhe7MGWBRi8aqsdEurLf9ttQEirfpLbtiK1owbwm8Cs8VuW6Mzj58tJbsh14dqHAMF7zoPuA2GWBcvxczdJTS17RpYMdNbJtwXqxGw9QD4brHwZr1SewAd1nyZwpVquYvSwNMaMbtYuowVXzXaJ9VBR4EBN3skqZLVpomXCFvzw34Ry2RDGHAKa4nRWsY6vTaQ1ZFhUPYQknj82ru4axs8zZyWSCHx5bsvc2Nj9TmaC9Paubwb4b9YFkrhGfwBQHrUybWxMepuSg5GnKQvV8MXVZUTJgEyWb72
Add each of the following new Gecko PlatformVM RPC endpoints to API-PlatformVM
platform.getTx
platform.getTxStatus
Just found out that avalanche.js is missing the info.isBootstrapped
method .
Mainnet is a good opportunity to remove the following known duplicate functionality.
The methods have been removed from gecko's Admin service and are only in it's Info service.
Mainnet is a good opportunity to sunset and remove the following known deprecated functionality.
Instead of returning an address, this function should return the key pair created.
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.