GithubHelp home page GithubHelp logo

graz-sh / graz Goto Github PK

View Code? Open in Web Editor NEW
102.0 7.0 26.0 9 MB

React hooks for Cosmos 🪐

Home Page: https://graz.sh/

License: MIT License

JavaScript 4.86% TypeScript 94.12% HTML 0.18% CSS 0.85%
graz keplr keplr-wallet react-cosmos-hooks use-keplr cosmjs cosmos cosmos-sdk leap leap-wallet

graz's People

Contributors

angeloanan avatar apollo-sturdy avatar beckettsean avatar bigs avatar burntval avatar callum-a avatar chudasamajd avatar codingki avatar cosmoscamel avatar dalechyn avatar findolor avatar gabrieljmf avatar grikomsn avatar j0nl1 avatar joshuanatanielnm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graz's Issues

Mobile Keplr & Leap workflows broken on Android

Describe the bug

Upon duplicating the code in the example project, graz does not auto-open the Keplr or Leap mobile app as expected, via WalletConnect, upon calling SigningCosmWasmClient.execute.

In the case of Keplr, the Chrome Android browser does not toggle into the mobile Keplr app. I and others who have tested must manually toggle into Keplr to approve the pending transaction.

When selecting Leap + WalletConnect on Android, the web app just opens up Leap in the Google Play store instead of going into the actual Leap app to approve the connection.

I can see that the mobile browser performs what looks like 2 requests in the background, as the loading bar seems to activate, but the app just stays on the current page while the Keplr app fails to launch.

To Reproduce

We're running a Next.js v12 app. Nothing special. To reproduce, just do the boilerplate graz setup and try executing a contract via Keplr+WalletConnect or Leap.

Trouble while connecting to wallet

Describe the bug

sup bros! we have another issue about connecting with graz.

  1. while connecting with wallet, I have 2 problems: window.keplr is not defined and error with gas price.

Screenshot 2023-04-24 at 17 32 26

Screen.Recording.2023-04-24.at.21.10.03.mp4

telegram-cloud-photo-size-2-5345868170843506598-x

  1. in 0.34 version we've used method "validateaddress" but in 0.35 version it doesn't work, could you please add this feature to new version? we also had a bunch of problems with prefix, so we decided to get it manually

Screenshot 2023-04-24 at 17 38 09

A clear and concise description of what the bug is.

...

To Reproduce

Steps to reproduce the behavior:

  1. Go to 'https://gutenberg.tools/#/create'
  2. Click on 'connect wallet'
  3. Check console
  4. See error

Expected behavior

A clear and concise description of what you expected to happen.

...

Screenshots

If applicable, add screenshots to help explain your problem.

...

Device

Please complete the following device information.
macOS, safari, chrome,brave

Desktop

  • OS: macOS
  • Browser safari, chrome,brave
  • Version: latest

Smartphone (haven't tried)

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context

Add any other context about the problem here.

...

Gas price must be set in the client options when auto gas is used

TLDR: I cannot instantiate a contract using useInstantiateContract or useSigningClients because it keeps calling cosmWasm.instantiate with auto gas and no gas price somewhere in the back, disregarding my configuration and client options.


Related issue: #7

I'm getting the following error regarding gas price when trying to instantiate a contract.

ContractCreate.tsx:23 Error: Gas price must be set in the client options when auto gas is used.
    at assertDefined (assert.ts:10:11)
    at _SigningCosmWasmClient.signAndBroadcast (signingcosmwasmclient.ts:548:7)
    at _SigningCosmWasmClient.instantiate (signingcosmwasmclient.ts:321:31)
    at lt3 (index.mjs:1:28075)
    at Object.mutationFn (index.mjs:1:41853)
    at Object.fn (mutation.ts:179:31)
    at run3 (retryer.ts:147:31)
    at createRetryer (retryer.ts:204:5)
    at executeMutation (mutation.ts:174:22)
    at Mutation.execute (mutation.ts:216:26)

This is strange, since gas price was already configured in the chain:

export const customChains = defineChains({
  chainName: {
    gas: {
      price: "1000000",
      denom: "udenom",
    },
    ...
});
configureGraz({
  defaultChain: customChains.chainName,
});

Here's what I tried with useInstantiateContract:

export default function ContractCreate() {
  const { instantiateContract } = useInstantiateContract({
    codeId: 5,
    onSuccess: ({ contractAddress }) => console.log("Address: ", contractAddress),
  });

  const handleClick = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const label = (event.currentTarget.elements.namedItem("label") as HTMLInputElement).value;
    const program = (event.currentTarget.elements.namedItem("program") as HTMLTextAreaElement).value;
    const msg = { program: btoa(program) };
    const options = { admin: ADMIN_ADDRESS };
    instantiateContract({ msg, options, label, });
  };

  return (
    <div>
      <h3>Create Contract</h3>
      <form onSubmit={handleClick}>
        <label htmlFor="program">program</label>
        <textarea id="program" />
        <label htmlFor="label">label</label>
        <input id="label" type="text" />
        <button type="submit">Create Contract</button>
      </form>
    </div>
  );
}

I then tried with cosmWasm from useSigningClients:

export default function ContractCreate() {
  const { signer } = useOfflineSigners();
  const signingClientsArgs = {
    cosmWasmSignerOptions: {
      gasPrice: GasPrice.fromString("0.012udenom"),
    },
    rpc: "https://api.somewhere.network:443/rpc",
    offlineSignerAuto: signer!,
  };
  const { data } = useSigningClients(signer ? signingClientsArgs : undefined);
  const { cosmWasm } = data || {};

  const handleClick = (event: React.FormEvent<HTMLFormElement>) => {
    if (!cosmWasm) return;
    event.preventDefault();
    const label = (event.currentTarget.elements.namedItem("label") as HTMLInputElement).value;
    const program = (event.currentTarget.elements.namedItem("program") as HTMLTextAreaElement).value;
    const msg = { program: btoa(program) };
    const options = { admin: ADMIN_ADDRESS };
    cosmWasm.instantiate(ADMIN_ADDRESS, 5, msg, label, 1000000, options);
  };

  return (
    <div>
      <h3>Create Contract</h3>
      <form onSubmit={handleClick}>
        <label htmlFor="program">program</label>
        <textarea id="program" />
        <label htmlFor="label">label</label>
        <input id="label" type="text" />
        <button type="submit">Create Contract</button>
      </form>
    </div>
  );
}

and getting the same error

assert.ts:10 Uncaught (in promise) Error: Gas price must be set in the client options when auto gas is used.
    at assertDefined (assert.ts:10:11)
    at _SigningCosmWasmClient.signAndBroadcast (signingcosmwasmclient.ts:548:7)
    at _SigningCosmWasmClient.instantiate (signingcosmwasmclient.ts:321:31)
    at handleClick (LawStoneCreate.tsx:61:14)
    at HTMLUnknownElement.callCallback2 (react-dom.development.js:4164:14)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:16)
    at invokeGuardedCallback (react-dom.development.js:4277:31)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4291:25)
    at executeDispatch (react-dom.development.js:9041:3)
    at processDispatchQueueItemsInOrder (react-dom.development.js:9073:7)

Note that I also called cosmWasm.instantiate with fee 1000000 instead of auto, yet still get the same error

Add support for signArbitrary

Is your feature request related to a problem? Please describe.

Add support for signArbitrary method that follows cosmos ADR36

Describe the solution you'd like

A method to sign arbitrary data similar to cosmos-kit
https://github.com/cosmology-tech/cosmos-kit/blob/162e087be99508a4bd560f7ffdd45d11a51a4801/wallets/keplr-extension/src/extension/client.ts#L148

Describe alternatives you've considered

I've tried to get access to the client/signer directly but can't find a way to get access to similar method.

Additional context

useCheckWallet returns true even if keplr not installed

Describe the bug

When you have leap wallet extension installed but not keplr, useCheckWallet(WalletType.KEPLR) return true and getWallet(WalletType.KEPLR) works (and should not) using Leap so it opens Leap modal

To Reproduce

Steps to reproduce the behavior:

  1. Install Leap extension only
  2. Select Keplr extension within any app (it should not be available as the extension is not installed)
  3. It opens Leap modal

Expected behavior

A clear and concise description of what you expected to happen.

useCheckWallet(WalletType.KEPLR) should return false and getWallet(WalletType.KEPLR) should throw an error.

Device

Please complete the following device information.

Desktop

  • OS: MacOS Ventura
  • Browser Brave
  • Version 1.52

Multichain experience

Is your feature request related to a problem? Please describe.

graz cannot connect to multiple chains, it switched the connection

Describe the solution you'd like

graz can connect to multiple chains at the same time

Add TInitial and TSuccess in MutationEventArgs

    I try to implement this, but I got this typescript error
const { sendTokensAsync, isLoading } = useSendTokens({
    onError: (err, args: SendTokensArgs) => {
      return toast({
        status: "error",
        title: "Send token fail",
        description: `Failed send token to ${args.recipientAddress}`,
      });
    },
  });

error
image

Originally posted by @joshuanatanielnm in #43 (comment)

State reloads when Keplr TX window pops up

The Keplr TX window (to approve or deny) of course opens a new window, this forces a state refresh and often leads to local state being overriden. It feels like this refresh is too oppressive when compared with other setups.

To reproduce:

  • Load some state from chain. (In my case it was some smart contract data)
  • Allow the user to edit it locally.
  • Attempt to sign a TX (in my case it was saving said changes to chain via executing a contract route)
  • The page will force a reload and local changes will be lost.

Excessive RPC clients created on connect & page load

Describe the bug

Whenever you connect a wallet (e.g. via Keplr), Graz is clearly creating an unnecessary number of RPC clients internally, probably via CosmWasmClient.connect. If you look at network activity in the browser, you'll notice at least 4 concurrent requests to the RPC provider, targeting its "status" endpoint, which is what the CosmWasmClient.connect method automatically does each time it is called. This is not good. Depending on the RPC provider, this can trigger an HTTP 429 rate limit error. Instead, you guys really ought to manage a singleton client and make use of the batch RPC client available in cosmjs.

...

To Reproduce

Steps to reproduce the behavior:

  1. Open dev console in browser.
  2. Navigate to your demo app.
  3. Open "Network Activity" tab
  4. Connect wallet via Keplr.
  5. Notice the exessive XHR requests sent to the RPC's "status" method.

Expected behavior

There should be only one request to the "status" method globally.

A clear and concise description of what you expected to happen.

...

Screenshots

image

Cannot use leap cosmos metamask snap with testnets

Describe the bug

I succeed to use Leap Metamask snap with Cosmos Hub, Osmosis and other mainnet chains, but if I use one from a testnet, it did not work. I got "Invalid chainId error".

To Reproduce

Steps to reproduce the behavior:

  1. Use the Next example, and look at _app.tsx file
  2. Let's assume ChainInfo is generated (yarn graz generate -g), import testnetChains from "graz/chains"
  3. Provide grazOptions={{ defaultChain: testnetChains.okp4testnet }} in GrazProvider
  4. Run, open it in a browser, connect with Leap Metamask Snap
  5. See error in Console Tab from the Dev tools

Expected behavior

Expect Connection with Leap Metamask Snap to work with any testnet as well as with any mainnet.

Additional context

Try with any available network from testnetChains

Smart contract interactions

Is your feature request related to a problem? Please describe.

It's common for React-based Dapps to want to interact with CosmWasm smart contracts on chains that support them. It would be great to manage these interactions—queries and mutations—with graz.

Describe the solution you'd like

A set of hooks:

  • useInstantiateContract—a mutator to instantiate a smart contract with a message provided by the caller.
  • useExecuteContract—a mutator to execute a smart contract, passing it a message provided by the caller.
  • useContractQueryRaw and useContractQuery (not yet sketched)—Hooks to query the state stored within a smart contract.

Ideally, these hooks use react-query to give users caching support, like the other hooks present in graz.

Describe alternatives you've considered

In the MVP we built of our Dapp, we've used cosmjs directly to query and transact with CosmWasm smart contracts. This, unfortunately, required us to use another state manager (Redux) to avoid constant refetching of data. We also wound up re-creating many of the abstractions provided by react-query and graz to provider the developer experience we wanted.

Additional context

As you can see, we've already sketched out potential implementations for execute and instantiate, but we want to make sure this is a community effort and in line with the thinking/spirit of the graz project, so we wanted to open discussion here to make sure we're all on the same page. Big fan of what you're doing with graz, and we plan on using it extensively in our dapp(s).

Auto reconnect options

Currently when we connect, _reconnect set to true, We need to make this optionable.

Possible solutions:

  • Add autoReconnect params inside connect
  • Add autoReconnect to configureGraz and GrazProvider options

Error with node_modules inside graz

Describe the bug

hey, I have an issue: when I use imports from graz/dist is doesn't work. I've checked every lib but I think the problem is here. Also when I'm trying to build project on production (you can see https://gutenberg.tools/) the variable isConnected doesn't work on prod but works on localhost (you can try to clone the repo https://github.com/diekuche/gutenberg and run it). I guess the problem is in node modules that I can't import in components via graz.

A clear and concise description of what the bug is.

window.keplr is not initialised
...

To Reproduce

Steps to reproduce the behavior:

  1. Go to 'https://gutenberg.tools/'
  2. Click on 'connect wallet'
  3. See error

Expected behavior

A clear and concise description of what you expected to happen.

...

Screenshots

Screenshot 2023-04-07 at 17 17 29

If applicable, add screenshots to help explain your problem.

Screenshot 2023-04-07 at 17 18 12

...

Device

Please complete the following device information.

Desktop

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context

Add any other context about the problem here.

...

Add contribution guide

Is your feature request related to a problem? Please describe.

Having a contribution guide would help new contributors and existing maintainers to keep tabs on how to maintain this project.

Describe the solution you'd like

Creating a CONTRIBUTING.md file on the root project which describes the contribution guide.

Describe alternatives you've considered

-

Additional context

-

Gas price must be set in the client options when auto gas is used

When using "auto" gas with the cosmwasm client it errors saying the gas prices must be set in the client config if using auto.
Error: Gas price must be set in the client options when auto gas is used.

Steps to reproduce:

  1. Get a client using the client hook.
  2. Attempt to execute a contract using the "auto" fee options
try {
  await client?.execute(
    account.bech32Address || '',
    config.CONTRACT_ADDRESS,
    { delete_template: { id } },
    'auto'
  );
} catch (err) {
  console.log(err);
}

Error while building project with graz

Describe the bug

when i use graz cli -g to generate list of chains there's an error in compiler about 8-ball chain
telegram-cloud-photo-size-2-5280528846254360298-y
Screenshot 2023-04-03 at 10 16 02
telegram-cloud-photo-size-2-5285249169931485926-y

A clear and concise description of what the bug is.
when I change 8ball to eightball on localhost, I guess everything works
...

To Reproduce

Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior

A clear and concise description of what you expected to happen.

...

Screenshots

If applicable, add screenshots to help explain your problem.

...

Device

Please complete the following device information.

Desktop

  • OS: MacOS
  • Browser: Brave
  • Version [e.g. 22]

Smartphone

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context

Add any other context about the problem here.

...

Naming things

  • hooks/useStakedBalances, actions/getStakedBalances naming is missleading because from the stargate client is called getBalanceStaked notice that our naming is balances when it should be just balance and it just returns one Coin

Vectis Wallet integration

Is your feature request related to a problem? Please describe.

It's related with a wallet integration question.

A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
I've been working on add vectis extension into graz library but I found some limitations.

First limitation is wallet interface only is compatible with the Keplr one.
https://github.com/strangelove-ventures/graz/blob/dev/packages/graz/src/types/wallet.ts#L26

Second limitation is about adaptersI don't see any documentation or code where they are being used. I guess these adapters solve the interface issue.

Describe the solution you'd like

A clear and concise description of what you want to happen.

It would be great instead of define the wallet inside graz library, receive the wallets externally (using these adapters) and fit to the interface.

Describe alternatives you've considered

A clear and concise description of any alternative solutions or features you've considered.

...

Additional context

Add any other context or screenshots about the feature request here.

Integration commit: nymlab@48d3a55

Any kind of help would be really useful to complete the integration.

A new name?

Just curious: a) where the name comes from and b) whether there's appetite for changing it, or considering alternatives.

These hooks could really lower the barrier to entry for building front-end stuff in Cosmos, but the name isn't doing it any favours re: discoverability. Even prefixing react- onto the library name per React conventions could be helpful, maybe?? Nit-picky, but wanted to have the discussion!

Rework method useQuery hooks to enabled only when signclients/clients/signers ready

Example:
from

export const useBalances = (bech32Address?: string): UseQueryResult<Coin[]> => {
  const { data: account } = useAccount();
  const address = bech32Address || account?.bech32Address;

  const queryKey = ["USE_BALANCES", address] as const;
  const query = useQuery(queryKey, ({ queryKey: [, _address] }) => getBalances(_address!), {
    enabled: Boolean(address),
    refetchOnMount: false,
    refetchOnReconnect: true,
    refetchOnWindowFocus: false,
  });

  return query;
};

to

export const useBalances = (bech32Address?: string): UseQueryResult<Coin[]> => {
const { signingClients } = useGrazSessionStore.getState();
  const { data: account } = useAccount();
  const address = bech32Address || account?.bech32Address;

  const queryKey = ["USE_BALANCES", address] as const;
  const query = useQuery(queryKey, ({ queryKey: [, _address] }) => getBalances(_address!), {
    enabled: Boolean(address) || Boolean(signingClients),
    refetchOnMount: false,
    refetchOnReconnect: true,
    refetchOnWindowFocus: false,
  });

  return query;
};

Expose store

Some of our users needs to access the store since chrome extensions can't use react stuff in service worker

Auto reconnect issue

If we already suggest chain and connect and then remove the chain from keplr it will return a runtime error that client can't handle
How to reproduce:

  1. Auto reconnect on
  2. suggestChainAndConnect
  3. remove the chain from keplr
  4. refresh the page

`global is not defined` when using with vite

Describe the bug

Package throws the following error when trying to import on a vite react project:

_stream_readable.js:48 Uncaught ReferenceError: global is not defined
    at node_modules/readable-stream/lib/_stream_readable.js (_stream_readable.js:48:21)
    at __require2 (chunk-Q7D65HC4.js?v=6a1b80ec:18:50)
    at node_modules/readable-stream/readable-browser.js (readable-browser.js:1:28)
    at __require2 (chunk-Q7D65HC4.js?v=6a1b80ec:18:50)
    at node_modules/hash-base/index.js (index.js:3:17)
    at __require2 (chunk-Q7D65HC4.js?v=6a1b80ec:18:50)
    at node_modules/md5.js/index.js (index.js:3:16)
    at __require2 (chunk-Q7D65HC4.js?v=6a1b80ec:18:50)
    at node_modules/create-hash/browser.js (browser.js:3:11)
    at __require2 (chunk-Q7D65HC4.js?v=6a1b80ec:18:50)
node_modules/readable-stream/lib/_stream_readable.js @ _stream_readable.js:48
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/readable-stream/readable-browser.js @ readable-browser.js:1
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/hash-base/index.js @ index.js:3
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/md5.js/index.js @ index.js:3
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/create-hash/browser.js @ browser.js:3
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/bip39/src/index.js @ index.js:3
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/@cosmjs/launchpad/node_modules/@cosmjs/crypto/build/bip39.js @ bip39.ts:2
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/@cosmjs/launchpad/node_modules/@cosmjs/crypto/build/index.js @ index.ts:1
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/@cosmjs/launchpad/build/address.js @ address.ts:1
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/@cosmjs/launchpad/build/index.js @ index.ts:4
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/@keplr-wallet/cosmos/build/adr-36/amino.js @ amino.ts:1
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/@keplr-wallet/cosmos/build/adr-36/index.js @ index.ts:1
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/@keplr-wallet/cosmos/build/stargate/wrapper/index.js @ index.ts:4
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/@keplr-wallet/cosmos/build/stargate/index.js @ index.ts:3
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
node_modules/@keplr-wallet/cosmos/build/index.js @ index.ts:5
__require2 @ chunk-Q7D65HC4.js?v=6a1b80ec:18
(anonymous) @ index.mjs:1

To Reproduce

Steps to reproduce the behavior:

  1. Create a vite app with react typescript template
  2. yarn add graz
  3. Try to import
  4. See error

Expected behavior

No error

Screenshots

image

Device

Please complete the following device information.

Desktop

  • OS: MacOS
  • Browser Chrome
  • Version 103

Additional context

Vite might not have a polyfier/babel transfomer for global.

Non signing cosmwasm client hook

Would be nice to also include a non signing cosmwasm client (CosmwasmClient) that can be used without connecting a wallet e.g. to purely load data before connecting. This allows dApps to have a read only view prior to wallet connection.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.