Skip to content

Commit

Permalink
Showing 21 changed files with 856 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -183,6 +183,15 @@ jobs:
-X POST -d "{\"body\": \"${FORGE_COMMENT}\"}" \
"https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${PR_NUMBER}/comments"
exit 0
sdk-typescript-test:
docker:
- image: circleci/node:16.13.1-browsers
steps:
- checkout
- run: cd ./ecosystem/typescript/sdk && yarn install
- run: cd ./ecosystem/typescript/sdk && yarn lint
- run: cd ./ecosystem/typescript/sdk && yarn test
- run: cd ./ecosystem/typescript/sdk && yarn build

workflows:
### on bors action ###
@@ -200,6 +209,7 @@ workflows:
- e2e-test
- lint
- unit-test
- sdk-typescript-test
- docker-build-push:
context: aws-dev
- forge-k8s:
4 changes: 4 additions & 0 deletions ecosystem/typescript/sdk/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
packages/**/node_modules/
dist/**
**/*.test.ts
34 changes: 34 additions & 0 deletions ecosystem/typescript/sdk/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"airbnb-base",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
plugins: [
"@typescript-eslint",
],
rules: {
quotes: ["error", "double"],
"max-len": ["error", 120],
"import/extensions": [
"error",
"never",
],
"max-classes-per-file": ["error", 10],
},
settings: {
"import/resolver": {
node: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
},
},
},
};
14 changes: 14 additions & 0 deletions ecosystem/typescript/sdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.DS_Store
*/**/.DS_Store
npm-debug.log
.npm/
/coverage
/tmp
node_modules
.idea/
.history/
.vscode/
dist/
.nyc_output/
build/
yarn.lock
1 change: 1 addition & 0 deletions ecosystem/typescript/sdk/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
coverage
1 change: 1 addition & 0 deletions ecosystem/typescript/sdk/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v16.14.0n
85 changes: 85 additions & 0 deletions ecosystem/typescript/sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Aptos TS/JS SDK

[![Discord][discord-image]][discord-url]
[![NPM Package Version][npm-image-version]][npm-url]
[![NPM Package Downloads][npm-image-downloads]][npm-url]

You need to connect to an [Aptos](https:/github.com/aptos-labs/aptos-core/) node to use this library, or run one
yourself locally.

Please read the [documentation][docs] for more.

## Usage
```js
// In Node.js
const Aptos = require("aptos");

const aptos = new AptosClient("https://fullnode.devnet.aptoslabs.com/");
console.log(aptos);
> { ... }
```

There you go, now you can use it:

```js
const account = new AptosAccount();
const accountInfo = await client.getAccount(account.address());
```

### Usage with TypeScript
You can use `aptos` like any other TypeScript module:

```typescript
import { AptosAccount, AptosClient } from "aptos"
const aptos = new AptosClient("https://fullnode.devnet.aptoslabs.com/");
const account = new AptosAccount();
const accountInfo = await client.getAccount(account.address());
```

If you are using the types in a `commonjs` module, like in a Node app, you just have to enable `esModuleInterop`
and `allowSyntheticDefaultImports` in your `tsconfig` for types compatibility:

```js
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
...
```
### Requirements
- [Node.js](https://nodejs.org)
- [yarn](https://yarnpkg.com/)
```bash
sudo apt-get update
sudo apt-get install nodejs yarn
```
### Generating Types
Originally created with this:
```bash
$ npx swagger-typescript-api -p ../api/doc/openapi.yaml -o ./src/api --modular --axios --single-http-client
```
#### Changes to make after generation:
- OpenAPI/SpecHTML routes/types deleted as they're unneeded.
- There are a few type errors in the `http-client.ts` as the axios types are incomplete, that were fixed via `// @ts-ignore`


### Testing (jest)

```bash
yarn test
```


[repo]: https://github.com/aptos-labs/aptos-core
[npm-image-version]: https://img.shields.io/npm/v/aptos.svg
[npm-image-downloads]: https://img.shields.io/npm/dm/aptos.svg
[npm-url]: https://npmjs.org/package/aptos
[discord-image]: https://img.shields.io/discord/945856774056083548?label=Discord&logo=discord&style=flat~~~~
[discord-url]: https://discord.gg/aptoslabs

## Semantic versioning

This project follows [semver](https://semver.org/) as closely as possible
16 changes: 16 additions & 0 deletions ecosystem/typescript/sdk/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/** @type {import("ts-jest/dist/types").InitialOptionsTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
coveragePathIgnorePatterns: ["api/*"],
testPathIgnorePatterns: ["dist/*"],
collectCoverage: true,
coverageThreshold: {
global: {
branches: 90,
functions: 95,
lines: 95,
statements: 95,
},
},
};
51 changes: 51 additions & 0 deletions ecosystem/typescript/sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "aptos",
"description": "Aptos SDK",
"license": "Apache-2.0",
"engines": {
"node": ">=11.0.0"
},
"main": "./dist/aptos.min.js",
"types": "./dist/aptos.d.ts",
"scripts": {
"build": "tsc -p .",
"lint": "eslint \"**/*.ts\"",
"test": "jest",
"cov:clean": "rm -rf coverage"
},
"repository": {
"type": "git",
"url": "https://github.com/aptos-labs/aptos-core.git"
},
"homepage": "https://github.com/aptos-labs/aptos-core",
"bugs": {
"url": "https://github.com/aptos-labs/aptos-core/issues"
},
"author": "aptoslabs.com",
"keywords": [
"Aptos",
"Aptos Labs",
"Move"
],
"devDependencies": {
"@types/jest": "^27.4.1",
"@types/node": "^17.0.23",
"@typescript-eslint/eslint-plugin": "^5.17.0",
"@typescript-eslint/parser": "^5.17.0",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.4",
"jest": "^27.5.1",
"ts-jest": "^27.1.4",
"ts-loader": "^9.2.8",
"ts-node": "^10.7.0",
"typescript": "^4.6.3"
},
"dependencies": {
"axios": "^0.26.1",
"buffer": "^6.0.3",
"js-sha3": "^0.8.0",
"tweetnacl": "^1.0.3"
},
"version": "0.0.9"
}
48 changes: 48 additions & 0 deletions ecosystem/typescript/sdk/src/aptos_account.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import AptosAccount, { AptosAccountPrivateKeyObject } from "./aptos_account";

const aptosAccountPrivateKeyObject: AptosAccountPrivateKeyObject = {
"address": "0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa",
"privateKeyHex": "0xc5338cd251c22daa8c9c9cc94f498cc8a5c7e1d2e75287a5dda91096fe64efa5de19e5d1880cac87d57484ce9ed2e84cf0f9599f12e7cc3a52e4e7657a763f2c",
"publicKeyHex": "0xde19e5d1880cac87d57484ce9ed2e84cf0f9599f12e7cc3a52e4e7657a763f2c",
};


test("generates random accounts", () => {
const a1 = new AptosAccount();
const a2 = new AptosAccount();
expect(a1.authKey()).not.toBe(a2.authKey());
expect(a1.address().hex()).not.toBe(a2.address().hex());
});

test("accepts custom address", () => {
const address = "0x777";
const a1 = new AptosAccount(null, address);
expect(a1.address().hex()).toBe(address);
});

test("Deserializes from AptosAccountPrivateKeyObject", () => {
const a1 = AptosAccount.fromAptosAccountPrivateKeyObject(aptosAccountPrivateKeyObject);
expect(a1.address().hex()).toBe(aptosAccountPrivateKeyObject.address);
expect(a1.pubKey().hex()).toBe(aptosAccountPrivateKeyObject.publicKeyHex);
});

test("Deserializes from AptosAccountPrivateKeyObject without address", () => {
const privateKeyObject = { privateKeyHex: aptosAccountPrivateKeyObject.privateKeyHex };
const a1 = AptosAccount.fromAptosAccountPrivateKeyObject(privateKeyObject);
expect(a1.address().hex()).toBe(aptosAccountPrivateKeyObject.address);
expect(a1.pubKey().hex()).toBe(aptosAccountPrivateKeyObject.publicKeyHex);
});

test("Serializes/Deserializes", () => {
const a1 = new AptosAccount();
const a2 = AptosAccount.fromAptosAccountPrivateKeyObject(a1.toPrivateKeyObject());
expect(a1.authKey().hex()).toBe(a2.authKey().hex());
expect(a1.address().hex()).toBe(a2.address().hex());
});

test("Signs Strings", () => {
const a1 = AptosAccount.fromAptosAccountPrivateKeyObject(aptosAccountPrivateKeyObject);
expect(a1.signHexString("0x77777").hex())
.toBe(
"0xc5de9e40ac00b371cd83b1c197fa5b665b7449b33cd3cdd305bb78222e06a671a49625ab9aea8a039d4bb70e275768084d62b094bc1b31964f2357b7c1af7e0d");
});
72 changes: 72 additions & 0 deletions ecosystem/typescript/sdk/src/aptos_account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as Nacl from "tweetnacl";
import * as SHA3 from "js-sha3";
import { Buffer } from "buffer/"; // the trailing slash is important!
import { HexString, MaybeHexString } from "./hex_string";
import Types from "./types";

export interface AptosAccountPrivateKeyObject {
address?: string,
publicKeyHex?: Types.HexEncodedBytes,
privateKeyHex: Types.HexEncodedBytes,
}

export default class AptosAccount {
readonly signingKey: Nacl.SignKeyPair;

private readonly accountAddress: HexString;

private authKeyCached?: HexString;

static fromAptosAccountPrivateKeyObject(obj: AptosAccountPrivateKeyObject): AptosAccount {
return new AptosAccount(HexString.ensure(obj.privateKeyHex).toUint8Array(), obj.address);
}

/** This class allows passing in an address, to handle account key rotation, where auth_key != public_key */
constructor(privateKeyBytes?: Uint8Array | undefined, address?: MaybeHexString) {
if (privateKeyBytes) {
this.signingKey = Nacl.sign.keyPair.fromSeed(privateKeyBytes.slice(0, 32));
} else {
this.signingKey = Nacl.sign.keyPair();
}
this.accountAddress = HexString.ensure(address || this.authKey().hex());
}

/** Returns the address associated with the given account */
address(): HexString {
return this.accountAddress;
}

/** Returns the authKey for the associated account */
authKey(): HexString {
if (!this.authKeyCached) {
const hash = SHA3.sha3_256.create();
hash.update(Buffer.from(this.signingKey.publicKey));
hash.update("\x00");
this.authKeyCached = new HexString(hash.hex());
}
return this.authKeyCached;
}

/** Returns the public key for the associated account */
pubKey(): HexString {
return HexString.ensure(Buffer.from(this.signingKey.publicKey).toString("hex"));
}

signBuffer(buffer: Buffer): HexString {
const signature = Nacl.sign(buffer, this.signingKey.secretKey);
return HexString.ensure(Buffer.from(signature).toString("hex").slice(0, 128));
}

signHexString(hexString: MaybeHexString): HexString {
const toSign = HexString.ensure(hexString).toBuffer();
return this.signBuffer(toSign);
}

toPrivateKeyObject(): AptosAccountPrivateKeyObject {
return {
address: this.address().hex(),
publicKeyHex: this.pubKey().hex(),
privateKeyHex: HexString.fromUint8Array(this.signingKey.secretKey.slice(0, 32)).hex(),
};
}
}
Loading

0 comments on commit abd8f0a

Please sign in to comment.