Code Monkey home page Code Monkey logo

name-wrapper's Introduction

This repo is deprecated and has been merged into https://github.com/ensdomains/ens-contracts/tree/master/contracts/wrapper

ENS Name Wrapper

The ENS Name Wrapper is a smart contract that wraps existing ENS names, providing several new features:

  • Wrapped names are ERC1155 tokens
  • Better permission control over wrapped names
  • Consistent API for names at any level of the hierarchy

In addition to implementing ERC1155, wrapped names have an ERC721-compatible ownerOf function to return the owner of a wrapped name.

Making ENS names ERC1155 compatible allows them to be displayed, transferred and traded in any wallet that supports the standard.

NameWrapper implements the optional ERC1155 metadata extension; presently this is via an HTTPS URL to a service ENS operates, but this can be changed in future as better options become available.

With the exception of the functionality to upgrade the metadata generation for tokens, there is no upgrade mechanism or centralised control over wrapped names.

Wrapping a name

.eth 2LDs (second-level domains) such as example.eth can be wrapped by calling wrapETH2LD(label, wrappedOwner, fuses, resolver). label is the first part of the domain name (eg, 'example' for example.eth), wrappedOwner is the desired owner for the wrapped name, and fuses is a bitfield representing permissions over the name that should be irrevoacably burned (see 'Fuses' below). A fuses value of 0 represents no restrictions on the name. The resolver can also optionally be set here and would need to be a wrapper aware resolver that uses the NameWrapper ownership over the Registry ownership.

In order to wrap a .eth 2LD, the owner of the name must have authorised the wrapper by calling setApprovalForAll on the registrar, and the caller of wrapETH2LD must be either the owner, or authorised by the owner on either the wrapper or the registrar.

All other domains (non .eth names as well as .eth subdomains such as sub.example.eth can be wrapped by calling wrap(parentNode, label, wrappedOwner, fuses). parentNode is the namehash of the name one level higher than the name to be wrapped, label is the first part of the name, wrappedOwner is the address that should own the wrapped name, and fuses is a bitfield representing permissions over the name that should be irrevocably burned (see 'Fuses' below). A fuses value of 0 represents no restrictions on the name. For example, to wrap sub.example.eth, you should call wrap(namehash('example.eth'), 'sub', owner, fuses).

In order to wrap a domain that is not a .eth 2LD, the owner of the name must have authorised the wrapper by calling setApprovalForAll on the registry, and the caller of wrap must be either the owner, or authorised by the owner on either the wrapper or the registry.

Wrapping a name by sending the .eth token

An alternative way to wrap .eth names is to send the name to the NameWrapper contract, this bypasses the need to setApprovalForAll on the registrar and is preferable when only wrapping one name.

To wrap a name by sending to the contract, you must use safeTransferFrom(address,address,uint256,bytes) with the extra data (the last parameter) ABI formatted as [string label, address owner, uint96 fuses, address resolver].

Example:

// Using ethers.js v5
abiCoder.encode(
  ['string', 'address', 'uint96', 'address'],
  ['vitalik', '0x...', '0x000000000000000000000001', '0x...']
)

Unwrapping a name

Wrapped names can be unwrapped by calling either unwrapETH2LD(label, newRegistrant, newController) or unwrap(parentNode, label, newController) as appropriate. label and parentNode have meanings as described under "Wrapping a name", while newRegistrant is the address that should own the .eth registrar token, and newController is the address that should be set as the owner of the ENS registry record.

Working with wrapped names

The wrapper exposes all the registry functionality via its own methods - setSubnodeOwner, setSubnodeRecord, setRecord, setResolver and setTTL are all implemented with the same functionality as the registry, and pass through to it after doing authorisation checks. Transfers are handled via ERC1155's transfer methods rather than mirroring the registry's setOwner method.

In addition, setSubnodeOwnerAndWrap and setSubnodeRecordAndWrap methods are provided, which create or replace subdomains while automatically wrapping the resulting subdomain.

All functions for working with wrapped names utilise ERC1155's authorisation mechanism, meaning an account that is authorised to act on behalf of another account can manage all its names.

Fuses

NameWrapper also implements a permissions mechanism called 'fuses'. Each name has a set of fuses representing permissions over that name. Fuses can be 'burned' either at the time the name is wrapped or at any subsequent time when the owner or authorised operator calls burnFuses. Once a fuse is burned, it cannot be 'unburned' - the permission that fuse represents is permanently revoked.

Before any fuses can be burned on a name, the parent name's "replace subdomain" fuse must first be burned. Without this restriction, any permissions revoked via fuses can be evaded by the parent name replacing the subdomain and then re-wrapping it with a more permissive fuse field. Likewise, when any fuses on a name are burned, the "unwrap" fuse must also be burned, to prevent the name being directly unwrapped and re-wrapped to reset the fuses. These restrictions have the effect of allowing applications to simply check the fuse value they care about on the name they are examining without having to be aware of the entire chain of custody up to the root.

The ENS root and the .eth 2LD are treated as having the "replace subdomain" and "unwrap" fuses burned. There is one edge-case here insofar as a .eth name's registration can expire; at that point the name can be purchased by a new registrant and effectively becomes unwrapped despite any fuse restrictions. When that name is re-wrapped, fuse fields can be set to a more permissive value than the name previously had. Any application relying on fuse values for .eth subdomains should check the expiration date of the .eth name and warn users if this is likely to expire soon.

The fuses field is 96 bits, and only 7 fuses are defined by the NameWrapper contract itself. Applications may use additional fuse bits to encode their own restrictions on applications. Any application wishing to do so should submit a PR to this README in order to record the use of the value and ensure there is no unintentional overlap.

Each fuse is represented by a single bit. If that bit is cleared (0) the restriction is not applied, and if it is set (1) the restriction is applied. Any updates to the fuse field for a name are treated as a logical-OR; as a result bits can only be set, never cleared.

CANNOT_UNWRAP = 1

If this fuse is burned, the name cannot be unwrapped, and calls to unwrap and unwrapETH2LD will fail.

CANNOT_BURN_FUSES = 2

If this fuse is burned, no further fuses can be burned. This has the effect of 'locking open' some set of permissions on the name. Calls to burnFuses will fail.

CANNOT_TRANSFER = 4

If this fuse is burned, the name cannot be transferred. Calls to safeTransferFrom and safeBatchTransferFrom will fail.

CANNOT_SET_RESOLVER = 8

If this fuse is burned, the resolver cannot be changed. Calls to setResolver and setRecord will fail.

CANNOT_SET_TTL = 16

If this fuse is burned, the TTL cannot be changed. Calls to setTTL and setRecord will fail.

CANNOT_CREATE_SUBDOMAIN = 32

If this fuse is burned, new subdomains cannot be created. Calls to setSubnodeOwner, setSubnodeRecord, setSubnodeOwnerAndWrap and setSubnodeRecordAndWrap will fail if they reference a name that does not already exist.

CANNOT_REPLACE_SUBDOMAIN = 64

If this fuse is burned, existing subdomains cannot be replaced by the parent name. Calls to setSubnodeOwner, setSubnodeRecord, setSubnodeOwnerAndWrap and setSubnodeRecordAndWrap will fail if they reference a name that already exists.

Checking Fuses using allFusesBurned(node, uint96)

To check whether or not a fuse is burnt you can use this function that takes a fuse mask of all fuses you want to check.

const areBurned = await allFusesBurned(
  namehash('vitalik.eth'),
  CANNOT_TRANSFER | CANNOT_SET_RESOLVER
)
// if CANNOT_UNWRAP AND CANNOT_SET_RESOLVER are *both* burned this will return true

Get current fuses and parent safety using getFuses(node)

Get fuses gets the raw fuses for a current node and also checks the parent hierarchy for you. The raw fuses it returns will be a uint96 and you will have to decode this yourself. If you just need to check a fuse has been burned, you can call allFusesBurned as it will use less gas.

The parent hierarchy check will start from the root and check 4 things:

  1. Is the registrant of the name the wrapper?
  2. Is the controller of the name the wrapper?
  3. Are the fuses burnt for replacing a subdomain?
  4. Is the name expired?

This is represented by enum NameSafety {Safe, Registrant, Controller, Fuses, Expired}

Lastly it will return to you the first node up the hierarchy that is vulnerable. After it finds a vulnerable node, it will break from checking and so it needs to be rechecked for children down the hierarchy once the vulnerable node has been made safe.

Installation and setup

npm install

Testing

npm run test

Any contract with 2 at the end, is referring to the contract being called by account2, rather than account1. This is for tests that require authorising another user.

Deploying test contracts into Rinkeby

Create .env

cp .env.org .env

### Set credentials

PRIVATE_KEY=
ETHERSCAN_API_KEY=
INFURA_API_KEY=

Please leave the following fields as blank

SEED_NAME=
METADATA_ADDRESS=
WRAPPER_ADDRESS=
RESOLVER_ADDRESS=

Run deploy script

yarn deploy:rinkeby will deploy to rinkeby and verify its source code

NOTE: If you want to override the default metadata url, set METADATA_HOST= to .env

$yarn deploy:rinkeby
yarn run v1.22.10
$ npx hardhat run --network rinkeby scripts/deploy.js
Deploying contracts to rinkeby with the account:0x97bA55F61345665cF08c4233b9D6E61051A43B18
Account balance: 1934772596667918724 true
{
  registryAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
  registrarAddress: '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85'
}
Setting metadata service to https://ens-metadata-service.appspot.com/name/0x{id}
Metadata address: 0x08f2D8D8240fC70FD777358b0c63e539714DD473
Wrapper address: 0x88ce50eFeA21996B20838d5E71994191562758f9
Resolver address: 0x784b7B9BA0Fc04b90187c06C0C7efC51AeA06aFB
wait for 5 sec until bytecodes are uploaded into etherscan
verify  0x08f2D8D8240fC70FD777358b0c63e539714DD473 with arguments https://ens-metadata-service.appspot.com/name/0x{id}
verify  0x88ce50eFeA21996B20838d5E71994191562758f9 with arguments 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e,0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85,0x08f2D8D8240fC70FD777358b0c63e539714DD473
verify  0x784b7B9BA0Fc04b90187c06C0C7efC51AeA06aFB with arguments 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e,0x88ce50eFeA21996B20838d5E71994191562758f9

After running the script it sets addresses to .env. If you want to redeploy some of contracts, remove the contract address from .env and runs the script again.

Seeding test data into Rinkeby

  1. Register a name using the account you used to deploy the contract
  2. Set the label (matoken for matoken.eth) to SEED_NAME= on .env
  3. Run yarn seed:rinkeby
~/.../ens/name-wrapper (seed)$yarn seed:rinkeby
yarn run v1.22.10
$ npx hardhat run --network rinkeby scripts/seed.js
Account balance: 1925134991223891632
{
  registryAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
  registrarAddress: '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85',
  wrapperAddress: '0x88ce50eFeA21996B20838d5E71994191562758f9',
  resolverAddress: '0x784b7B9BA0Fc04b90187c06C0C7efC51AeA06aFB',
  firstAddress: '0x97bA55F61345665cF08c4233b9D6E61051A43B18',
  name: 'wrappertest4'
}
Wrapped NFT for wrappertest4.eth is available at https://testnets.opensea.io/assets/0x88ce50eFeA21996B20838d5E71994191562758f9/42538507198368349158588132934279877358592939677496199760991827793914037599925
Wrapped NFT for sub2.wrappertest4.eth is available at https://testnets.opensea.io/assets/0x88ce50eFeA21996B20838d5E71994191562758f9/22588238952906792220944282072078294622689934598844133294480594786812258911617

name-wrapper's People

Contributors

jefflau avatar arachnid avatar makoto avatar gopi-gith avatar nxt3d avatar

Stargazers

ret2basic.eth avatar Joseph Kim avatar  avatar  avatar wayaway avatar Amin Bashiri avatar Prof. - XadK3 avatar  avatar lyfoc.eth avatar logonaut.eth avatar Merlin-721 avatar Vage  avatar Scobel avatar  avatar Kevin Alexander Scott Jellis avatar YungSB.eth avatar  avatar Z avatar Maintainer.eth avatar 0xlucy avatar Saul Moonves avatar  avatar  avatar  avatar  avatar Etch avatar John avatar  avatar 10allday avatar  avatar Shawn Mitchell avatar  avatar Must479 avatar bigint avatar Bakuchi avatar netop://ウエハ avatar codingsh avatar  avatar Michael Demarais avatar 0xchaosbi avatar  avatar  avatar witt avatar

Watchers

 avatar James Cloos avatar Richard Moore avatar  avatar Michael Demarais avatar  avatar Aman khan avatar David Magnuson avatar  avatar Khori Whitaker avatar  avatar

name-wrapper's Issues

Figure out a gas-optimised way to register-and-wrap .eth 2LDs

One option is providing a method to do this that wraps the registrar's register method, authorising the wrapper as a registrar controller, and then maintaining a list of controllers on the wrapper. Future controllers would call the wrapper instead of the registrar directly, and the wrapper could transfer names directly to itself during registration.

Limit wrapping privilege

Dear ENS team,
For the NameWrapper contract, with the current implementation, any instance of this contract will allow for the injection of new names into a collections environment. We are wondering if this is the desired functionality for all NameWrapper implementations or if there is the ability to provide access controls?

We would recommend adding the ability to add access controls to the NameWrapper contract so that the use case of tokenising a collection around a single ENS domain is possible. And preventing other users from injecting their domains into these collections.

Review (part1)

I reviewed the overall structure and up to line 1114 of test/nftFuseWrapper.js. Will post par2 once more document is added.

.travis

Please add CI

package.json

  • chai should be under devDependencies
  • Please update all package to the latest when possible.

eg:

  • ens is 0.4.5 but the latest is 0.6

  • @openzeppelin/contracts is 4.0.0 but the latest is 4.1.0

  • npm run compile doesn't compile anything

> [email protected] compile /Users/makoto/work/ens/ens-nft-fuse-wrapper
> hardhat compile

Solidity 0.8.0 is not fully supported yet. You can still use Hardhat, but some features, like stack traces, might not work correctly.

Learn more at https://hardhat.org/reference/solidity-support"

Nothing to compile
  • npm run deploy fails
~/.../ens/ens-nft-fuse-wrapper (master)$npm run deploy

> [email protected] deploy /Users/makoto/work/ens/ens-nft-fuse-wrapper
> hardhat run scripts/deploy.js && hardhat run scripts/publish.js

📡 Deploy 

📄 BytesUtil
HardhatError: HH700: Artifact for contract "BytesUtil" not found.
    at Artifacts._getArtifactPathFromFiles (/Users/makoto/work/ens/ens-nft-fuse-wrapper/node_modules/hardhat/src/internal/artifacts.ts:391:13)
    at Artifacts._getArtifactPath (/Users/makoto/work/ens/ens-nft-fuse-wrapper/node_modules/hardhat/src/internal/artifacts.ts:316:17)
    at Artifacts.readArtifact (/Users/makoto/work/ens/ens-nft-fuse-wrapper/node_modules/hardhat/src/internal/artifacts.ts:49:26)
    at getContractFactoryByName (/Users/makoto/work/ens/ens-nft-fuse-wrapper/node_modules/@nomiclabs/hardhat-ethers/src/internal/helpers.ts:100:20)
    at deploy (/Users/makoto/work/ens/ens-nft-fuse-wrapper/scripts/deploy.js:14:29)
    at main (/Users/makoto/work/ens/ens-nft-fuse-wrapper/scripts/deploy.js:71:24)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] deploy: `hardhat run scripts/deploy.js && hardhat run scripts/publish.js`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] deploy script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/makoto/.npm/_logs/2021-05-10T10_01_56_128Z-debug.log

## README

Please specify how to install, setup, how to run the doc, and API reference

scripts

  • remove deploy-original.js

Tests in general

No need to define these constant Fuses to each test (eg: line 875, 904, etc).
Define all in once

const CAN_DO_EVERYTHING = 0 const CANNOT_UNWRAP = await NFTFuseWrapper.CANNOT_UNWRAP() const CANNOT_REPLACE_SUBDOMAIN = await NFTFuseWrapper.CANNOT_REPLACE_SUBDOMAIN()

hardhat.config.js

  • add hardhat network endpoint

nftFuseWrapper.js

  • There are lots of variables defined but not in use (config, buffer, rootOwner, etc). You should have VS Studio plugin which shows unused vairables if you haven't already.

EnsRegistry2

What does EnsRegistry2 mean? Can you make more meaningful variable?
I wasn't quite sure how you are calling EnsRegistry and EnsRegistry2 differently.

Wraps a name if you are the owner

  • Would be good if you can also check the state before wrapping and assert it's either empty

emits correct events

  • Can you also assert the event input?

Does not allow wrapping .eth 2LDs.

It's trying to wrap `.abc, not .eth 2LDs.

    it('Does not allow wrapping .eth 2LDs.', async () => {
      const label = 'wrapped'
      const labelHash = labelhash(label)
      await BaseRegistrar.register(labelHash, account, 84600)
      await BaseRegistrar.setApprovalForAll(NFTFuseWrapper.address, true)
      expect(NFTFuseWrapper.wrap(ROOT_NODE, 'abc', 0, account2)).to.be.reverted
    })

'Does not allow an account authorised by the owner on the ENS registry to unwrap a name

      // allow account to deal with all account2's names
      await EnsRegistry2.setApprovalForAll(account, true)
      await EnsRegistry2.setApprovalForAll(NFTFuseWrapper.address, true)

this is more of question but does the order of NFTFuseWrapper matter? If the order is as follows, can the account override the NFTFuseWrapper hence can unwrap?

      await EnsRegistry2.setApprovalForAll(NFTFuseWrapper.address, true)
      await EnsRegistry2.setApprovalForAll(account, true)

wrapETH2LD

  • There is no test to make sure that you cannot wrap .xyz not subdomains.

Does not allows fuse to be burned if CANNOT_UNWRAP has not been burned.

  • There are 2 tests with identical test description.
  • Does not allow not allows
    it('Does not allows fuse to be burned if CANNOT_UNWRAP has not been burned.', async () => {
      const CANNOT_SET_DATA = await NFTFuseWrapper.CANNOT_SET_DATA()
      await BaseRegistrar.setApprovalForAll(NFTFuseWrapper.address, true)
      await BaseRegistrar.register(labelHash, account, 84600)
      await expect(
        NFTFuseWrapper.wrapETH2LD(label, CANNOT_SET_DATA, account)
      ).to.be.revertedWith(
        'revert NFTFuseWrapper: Cannot burn fuses: domain can be unwrapped'
      )
    })

    it('Does not allows fuse to be burned if CANNOT_UNWRAP has not been burned.', async () => {
      const CANNOT_UNWRAP = await NFTFuseWrapper.CANNOT_UNWRAP()
      const CANNOT_SET_DATA = await NFTFuseWrapper.CANNOT_SET_DATA()
      const initialFuses = CANNOT_UNWRAP | CANNOT_SET_DATA
      await BaseRegistrar.setApprovalForAll(NFTFuseWrapper.address, true)
      await BaseRegistrar.register(labelHash, account, 84600)
      await NFTFuseWrapper.wrapETH2LD(label, initialFuses, account)
      expect(await NFTFuseWrapper.getFuses(nameHash)).to.equal(initialFuses)
    })
  })

unwrapETH2LD()

  • There is no test to make suer that you cannot unwrap .xyz not subdomains.

Accepts a zero-length data field for no fuses

There is no assertion in the test

### Allows burning other fuses if CAN_UNWRAP has been burnt

Which part does it represent it is burnt?

      expect(await EnsRegistry.owner(wrappedTokenId)).to.equal(
        NFTFuseWrapper.address
      )
      expect(await NFTFuseWrapper.ownerOf(wrappedTokenId)).to.equal(account)
      expect(await NFTFuseWrapper.getFuses(wrappedTokenId)).to.equal(5)
      expect(await NFTFuseWrapper.canUnwrap(wrappedTokenId)).to.equal(false)

'Wraps a name transferred to it and sets the owner to the from address'

The test should assert that the BaseRegistrar.ownerOf('wrappedTokenId') belongs to NFTFuseWrapper.address

Rejects transfers where the data field is not 0 or 96 bits

Need explanation of why they gets rejected

Allows burning unknown fuses

  • What is 64?
  • I don't quite understand what this test is doing

Can be called by the owner.

At

Can be called by the owner.

1 should be put into the constant representing the type of the fuse.

Logically ORs passed in fuses with already-burned fuses.

      expect(await NFTFuseWrapper.getFuses(wrappedTokenId)).to.equal(
        CANNOT_UNWRAP | CANNOT_UNWRAP | CANNOT_REPLACE_SUBDOMAIN | 64
      )

The repetition of CANNOT_UNWRAP | CANNOT_UNWRAP seems wrong. Maybe CAN_DO_EVERYTHING | CANNOT_UNWRAP ?

Allows burning unknown fuses

  • What is 64?
  • I don't quite understand what this test is doing

burnFuses.can set fuses and burn canCreateSubdomains

Do you need to activate CANNOT_REPLACE_SUBDOMAIN in this test?

     await NFTFuseWrapper.wrapETH2LD(
        label,
        CAN_DO_EVERYTHING | CANNOT_UNWRAP | CANNOT_REPLACE_SUBDOMAIN,
        account
      )

Is 'createSubdomain is set to false' needed?

      expect(canCreateSubdomain1, 'createSubdomain is set to false').to.equal(
        true
      )

setRecord.Cannot be called if CANNOT_TRANSFER is burned

Why resolver/ttl are allowed to be called if CANNOT_TRANSFER is burned but only setRecord cannot be called?

Empty test

  describe('ERC1155', () => {
    // ERC1155 methods
    // Incorporate OpenZeppelin test suite.
  })

Contracts

NFTFuseWrapper.sol

  • Should probably define set of interfaceId so that dapp can inspect the registrant to check if it is now owned by FuseWrapper contract. Otherwise not sure how we determine if domain/subdomain is wrapped or not.

### _mint

should have a test making sure this event is minted

    emit TransferSingle(msg.sender, owner, address(0x0), tokenId, 1);

_burn

_mint is checking if owner is 0 or not. Do we need similar check?

    function _burn(uint256 tokenId) private {
        address owner = ownerOf(tokenId);

should have a test making sure this event is minted

    emit TransferSingle(msg.sender, owner, address(0x0), tokenId, 1);

_unwrap

owner != 0 check shoulld move to _burn as it is the rule of ERC1155

    function _unwrap(
        bytes32 parentNode,
        bytes32 label,
        address owner
    ) private {
        bytes32 node = makeNode(parentNode, label);
        require(
            owner != address(0x0),
            "NFTFuseWrapper: Target owner cannot be 0x0"
        );

ERC1155

safeBatchTransferFrom

No test covered by nftFuseWrapper.js

Open questions

  • What changes do we need on Subgraph?
  • What do NFT marketplaces need to do to prevent from 2 matoken.eth to appear on the search result for both wrapped and unrwapped version?
  • How do we prevent ( or make it clear) that the new owner of NFT have limited control if they buy wrapped Tokens which many of fuses are already burnt?
  • Write down the case when ENS name expires or DNSSEC proof is updated to new ETH address.
  • Is there any way to introspect from Registry/tegistrar

Opensea etherscan indexed variables.

Hello, ens team,

We have noticed display issues on NFT marketplaces and etherscan that could be fixed by adding a few variables.

image

The unidentified contract can be fixed by adding a variable "name" in the smart contract (maybe inside the erc1155Fuse.sol file) that the user could set up using the contract at contract deployment.
Screenshot 2022-07-18 at 12 21 19

The max Total supply issue on Opensea can be fixed by adding a function named "totalSupply()"
image

Thank you for your time.
Unfortunately, I cannot create a branch or submit a PR.

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.