Skip to content

Commit

Permalink
Initial implementation of the Armory SDK (#241)
Browse files Browse the repository at this point in the history
* base sdk

* config getter

* updated lock

* working example with sdk

* added makefile to sdk

* removed unused package

* re-installed packages to cleanup lock

* remove unused test directory

* add one true test

* added unit tests on utils, narrowed necessary config for evaluate/signRequest functions

* sdk with example for signMessage/Transaction/TypedData working

* Update packages/armory-sdk/src/lib/exceptions.ts

Co-authored-by: William Calderipe <wcalderipe@gmail.com>

* Update packages/armory-sdk/src/lib/exceptions.ts

Co-authored-by: William Calderipe <wcalderipe@gmail.com>

* Remove console.log

* Rename example project to armory-sdk-nodejs

---------

Co-authored-by: William Calderipe <wcalderipe@gmail.com>
  • Loading branch information
Ptroger and wcalderipe authored May 7, 2024
1 parent 363373d commit de49b47
Show file tree
Hide file tree
Showing 35 changed files with 1,252 additions and 3 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ include ./packages/nestjs-shared/Makefile
include ./packages/policy-engine-shared/Makefile
include ./packages/transaction-request-intent/Makefile
include ./packages/signature/Makefile
include ./packages/armory-sdk/Makefile

# For more terminal color codes, head over to
# https://opensource.com/article/19/9/linux-terminal-colors
Expand Down
4 changes: 2 additions & 2 deletions apps/vault/src/vault/http/rest/controller/sign.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Request } from '@narval/policy-engine-shared'
import { Body, Controller, Post, UseGuards } from '@nestjs/common'
import { createZodDto } from 'nestjs-zod'
import {
Expand Down Expand Up @@ -26,7 +25,8 @@ export class SignController {

@Post()
async sign(@ClientId() clientId: string, @Body() body: SignRequestDto) {
const request: Request = body.request
const parsed = SignRequest.parse(body)
const { request } = parsed
const result = await this.signingService.sign(clientId, request)

return { signature: result }
Expand Down
18 changes: 18 additions & 0 deletions examples/armory-sdk-nodejs/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
11 changes: 11 additions & 0 deletions examples/armory-sdk-nodejs/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: 'armory-sdk-nodejs',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }]
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/examples/armory-sdk-nodejs'
}
80 changes: 80 additions & 0 deletions examples/armory-sdk-nodejs/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"name": "armory-sdk-nodejs",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "examples/armory-sdk-nodejs/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"target": "node",
"compiler": "tsc",
"outputPath": "dist/examples/armory-sdk-nodejs",
"main": "examples/armory-sdk-nodejs/src/index.ts",
"tsConfig": "examples/armory-sdk-nodejs/tsconfig.app.json",
"webpackConfig": "examples/armory-sdk-nodejs/webpack.config.js"
},
"configurations": {
"development": {},
"production": {}
}
},
"serve": {
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"options": {
"buildTarget": "armory-sdk-nodejs:build"
},
"configurations": {
"development": {
"buildTarget": "armory-sdk-nodejs:build:development"
},
"production": {
"buildTarget": "armory-sdk-nodejs:build:production"
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["examples/armory-sdk-nodejs/**/*.ts"]
}
},
"test:type": {
"executor": "nx:run-commands",
"options": {
"command": "make basic-flow/test/type"
}
},
"test:unit": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "examples/armory-sdk-nodejs/jest.unit.ts",
"verbose": true
}
},
"test:integration": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "examples/armory-sdk-nodejs/jest.integration.ts",
"verbose": true,
"runInBand": true
}
},
"test:e2e": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "examples/armory-sdk-nodejs/jest.e2e.ts",
"verbose": true,
"runInBand": true
}
}
},
"tags": ["type:application"]
}
133 changes: 133 additions & 0 deletions examples/armory-sdk-nodejs/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* eslint-disable no-console */

import { createArmoryConfig, evaluate, importPrivateKey, signRequest } from '@narval/armory-sdk'
import { SignMessageAction, SignTransactionAction, SignTypedDataAction } from '@narval/policy-engine-shared'
import { privateKeyToJwk } from '@narval/signature'
import { resourceId } from 'packages/armory-sdk/src/lib/utils'
import { UNSAFE_PRIVATE_KEY } from 'packages/policy-engine-shared/src/lib/dev.fixture'
import { v4 } from 'uuid'
import { Hex, TypedData, createPublicClient, http, toHex } from 'viem'
import { privateKeyToAddress } from 'viem/accounts'
import { polygon } from 'viem/chains'

const main = async () => {
const anotherAddress = '0x3f843E606C79312718477F9bC020F3fC5b7264C2'.toLowerCase() as Hex

const config = createArmoryConfig({
authClientId: 'ad496b05-3a1e-4138-93d0-1505e7a5c8a1',
authHost: 'http://localhost:3010',
authSecret: '27948c192850f36eb0b45285eb1a9ec3490e6ee573dd0ad63a32fe42c317a18be3e29b38bd56663e47bb',
vaultClientId: 'd6369edd-7353-486c-9129-5f667fe8f3fc',
vaultHost: 'http://localhost:3011',
vaultSecret: 'f0e6ad88ba601f0e342e42d945b865d461293c587561a62d8f5cb86d442eff0288dc3e8ae6d98639aadb',
signer: privateKeyToJwk(UNSAFE_PRIVATE_KEY.Alice)
})

const privateKey = '0xcbdb5073d97f2971672e99769d12411fc044dde79b803e9c9e3ad6df5c9a260a'
const walletId = 'test-wallet-id'

console.log('\n\nimporting private key to vault...: ', privateKey)
const vaultWalletAddress = privateKeyToAddress(privateKey)
await importPrivateKey(config, { privateKey, walletId })

const nonce = 11
const transactionRequestAction: SignTransactionAction = {
action: 'signTransaction',
transactionRequest: {
from: vaultWalletAddress,
chainId: 137,
gas: BigInt(22000),
to: anotherAddress,
maxFeePerGas: BigInt(291175227375),
maxPriorityFeePerGas: BigInt(81000000000),
value: toHex(BigInt(50000)),
nonce
// Update it accordingly to USER_PRIVATE_KEY last nonce + 1.
// If you are too low, viem will error on transaction broadcasting, telling you what should be a correct nonce.
},
resourceId: resourceId(walletId),
nonce: v4()
}

const { accessToken } = await evaluate(config, transactionRequestAction)
const { signature } = await signRequest(config, { accessToken, request: transactionRequestAction })

const publicClient = createPublicClient({
chain: polygon,
transport: http()
})
try {
const hash = await publicClient.sendRawTransaction({ serializedTransaction: signature })
console.log('\n\ntransaction request successfully broadcasted !', 'txHash: ', hash)
} catch (error) {
console.error('transaction request failed', error)
}

const signMessageAction: SignMessageAction = {
action: 'signMessage',
message: 'Hello, World!',
resourceId: resourceId(walletId),
nonce: v4()
}

const { accessToken: messageAccessToken } = await evaluate(config, signMessageAction)
const { signature: messageSignature } = await signRequest(config, {
accessToken: messageAccessToken,
request: signMessageAction
})

console.log('\n\nmessage signature:', messageSignature)

try {
const messageVerification = await publicClient.verifyMessage({
message: signMessageAction.message,
signature: messageSignature,
address: vaultWalletAddress
})
console.log('\n\nmessage verification:', messageVerification)
} catch (error) {
console.error('message verification failed', error)
}

const signTypedDataAction: SignTypedDataAction = {
action: 'signTypedData',
typedData: {
types: {
EIP712Domain: [{ name: 'name', type: 'string' }],
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' }
]
},
primaryType: 'Person',
domain: { name: 'Ether Mail', version: '1' },
message: {
name: 'Bob',
wallet: anotherAddress
}
},
resourceId: resourceId(walletId),
nonce: v4()
}

const { accessToken: typedDataAccessToken } = await evaluate(config, signTypedDataAction)

const { signature: typedDataSignature } = await signRequest(config, {
accessToken: typedDataAccessToken,
request: signTypedDataAction
})

const typedDataVerification = await publicClient.verifyTypedData({
signature: typedDataSignature,
address: vaultWalletAddress,
types: signTypedDataAction.typedData.types as unknown as TypedData,
// TODO: @ptroger find a way to make this work without casting or explitly mapping to viem.
primaryType: signTypedDataAction.typedData.primaryType,
domain: signTypedDataAction.typedData.domain,
message: signTypedDataAction.typedData.message
})

console.log('\n\ntyped data verification:', typedDataVerification)
}

main()
10 changes: 10 additions & 0 deletions examples/armory-sdk-nodejs/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["node"]
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}
16 changes: 16 additions & 0 deletions examples/armory-sdk-nodejs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}
9 changes: 9 additions & 0 deletions examples/armory-sdk-nodejs/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"]
}
8 changes: 8 additions & 0 deletions examples/armory-sdk-nodejs/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { composePlugins, withNx } = require('@nx/webpack')

// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
// Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())`
return config
})
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,8 @@
"viem": "^2.7.16",
"wagmi": "^2.5.7",
"zod": "^3.22.4"
},
"nx": {
"includedScripts": []
}
}
25 changes: 25 additions & 0 deletions packages/armory-sdk/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
}
}
]
}
29 changes: 29 additions & 0 deletions packages/armory-sdk/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
SIGNATURE_PROJECT_NAME := armory-sdk
SIGNATURE_PROJECT_DIR := ./packages/armory-sdk

# == Code format ==

armory-sdk/format:
npx nx format:write --projects ${SIGNATURE_PROJECT_NAME}

armory-sdk/lint:
npx nx lint ${SIGNATURE_PROJECT_NAME} -- --fix

armory-sdk/format/check:
npx nx format:check --projects ${SIGNATURE_PROJECT_NAME}

armory-sdk/lint/check:
npx nx lint ${SIGNATURE_PROJECT_NAME}

# == Testing ==

armory-sdk/test/type:
npx tsc \
--project ${SIGNATURE_PROJECT_DIR}/tsconfig.lib.json \
--noEmit

armory-sdk/test/unit:
npx nx test:unit ${SIGNATURE_PROJECT_NAME} -- ${ARGS}

armory-sdk/test/unit/watch:
make armory-sdk/test/unit ARGS=--watch
11 changes: 11 additions & 0 deletions packages/armory-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# armory-sdk

This library was generated with [Nx](https://nx.dev).

## Building

Run `nx build armory-sdk` to build the library.

## Running unit tests

Run `nx test armory-sdk` to execute the unit tests via [Jest](https://jestjs.io).
Loading

0 comments on commit de49b47

Please sign in to comment.