Skip to content

Commit

Permalink
Merge pull request #21 from trustenterprises/feature-token-issuance
Browse files Browse the repository at this point in the history
Beta token issuance
  • Loading branch information
mattsmithies authored May 28, 2021
2 parents 38a7297 + 98a2b08 commit 152b0f4
Show file tree
Hide file tree
Showing 8 changed files with 493 additions and 251 deletions.
18 changes: 18 additions & 0 deletions __tests__/e2e/hashgraph_client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,21 @@ test("The client can update the memo of a private topic", async () => {
const updatedTopicInfo = await client.getTopicInfo(newTopic.topic)
expect(updatedTopicInfo.topicMemo).toBe(newMemo)
}, 20000)

test("The client can create a token", async () => {

const tokenData = {
supply: "10",
name: 'e2e-hedera-token-test',
symbol: 'te-e2e',
memo: 'THIS IS A MEMO',
}

const token = await client.createToken(tokenData)

expect(token.tokenId).toBeDefined()
expect(token.memo).toBe(tokenData.memo)
expect(token.supply).toBe(tokenData.supply)
expect(token.symbol).toBe(tokenData.symbol)
expect(token.name).toBe(tokenData.name)
}, 20000)
36 changes: 36 additions & 0 deletions app/handler/createTokenHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import createTokenRequest from "app/validators/createTokenRequest"
import Response from "app/response"
import Specification from "app/hashgraph/tokens/specifications"

async function CreateTokenHandler(req, res) {
const validationErrors = createTokenRequest(req.body)

if (validationErrors) {
return Response.unprocessibleEntity(res, validationErrors)
}

const {
symbol,
name,
supply,
memo,
requires_kyc = false,
can_freeze = false
} = req.body

const { hashgraphClient } = req.context

const token = await hashgraphClient.createToken({
specification: Specification.Fungible,
memo,
name,
symbol,
supply,
requires_kyc,
can_freeze
})

Response.json(res, token)
}

export default CreateTokenHandler
67 changes: 66 additions & 1 deletion app/hashgraph/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import {
TopicUpdateTransaction,
TopicMessageSubmitTransaction,
TransactionRecordQuery,
TopicId
TopicId,
TokenCreateTransaction,
Hbar,
HbarUnit
} from "@hashgraph/sdk"
import HashgraphClientContract from "./contract"
import HashgraphNodeNetwork from "./network"
import Config from "app/config"
import sleep from "app/utils/sleep"
import Explorer from "app/utils/explorer"
import sendWebhookMessage from "app/utils/sendWebhookMessage"
import Specification from "app/hashgraph/tokens/specifications"

class HashgraphClient extends HashgraphClientContract {
// Keep a private internal reference to SDK client
Expand Down Expand Up @@ -139,6 +143,67 @@ class HashgraphClient extends HashgraphClientContract {

return messageTransactionResponse
}

createToken = async tokenCreation => {
const {
specification = Specification.Fungible,
accountId,
memo,
name,
symbol,
supply,
requires_kyc = false,
can_freeze = false
} = tokenCreation

const client = this.#client

const operatorPrivateKey = PrivateKey.fromString(Config.privateKey)
const supplyPrivateKey = PrivateKey.fromString(Config.privateKey)

const supplyWithDecimals = supply * 10 ** specification.decimals

const transaction = new TokenCreateTransaction()
.setTokenName(name)
.setTokenSymbol(symbol)
.setTreasuryAccountId(accountId || Config.accountId)
.setInitialSupply(supplyWithDecimals)
.setDecimals(specification.decimals)
.setFreezeDefault(false)
.setMaxTransactionFee(new Hbar(5, HbarUnit.Hbar)) //Change the default max transaction fee

if (memo) {
transaction.setTokenMemo(memo)
transaction.setTransactionMemo(memo)
}

if (requires_kyc) {
transaction.setKycKey(operatorPrivateKey.publicKey)
}

if (can_freeze) {
transaction.setFreezeKey(operatorPrivateKey.publicKey)
}

transaction.freezeWith(client)

const signTx = await (await transaction.sign(operatorPrivateKey)).sign(
supplyPrivateKey
)

const txResponse = await signTx.execute(client)
const receipt = await txResponse.getReceipt(client)

return {
name,
symbol,
memo,
reference: specification.reference,
supply: String(supply),
supplyWithDecimals: String(supplyWithDecimals),
tokenId: receipt.tokenId.toString()
}
}
}

export default HashgraphClient
97 changes: 97 additions & 0 deletions app/hashgraph/tokens/specifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* Update the type definition of Specification when new bits are required
*/
// export type Specification = {
// reference: string;
// decimals: number;
// kyc: boolean;
// wipe: boolean;
// freeze: boolean;
// }

/**
* TODO: more conversations and design about interop and bridging required to reduce precision loss
*
* This is a prototype spec that describes the interop for trading
* imported ERC20 tokens with minted HTS tokens. This provides a
* common shared attributes, the decimal places have been reduced to
* 6 as we wish to have enough precision but not lose the max limit
* for (2 - 1) * (10 ** 64)
*
* As at 01/02/21 provides a precision loss of 1.3 cents for 0.000001 eth
*
* With 6 digits of precision we have have a max HTS limit of for this spec
*
* 9,223,372,036,854.775 (9 trillion)
*
* @type {{decimals: number}}
*/
const Fungible = {
reference: "basic.fungible",
decimals: 6,
kyc: false,
wipe: false,
freeze: false
}

/**
* When creating NFT representations of tokens we require a token to be unique
* and non fungible, it cannot be divided.
*
* @type {{decimals: number}}
*/
const NonFungible = {
reference: "basic.nonfungible",
decimals: 0,
kyc: false,
wipe: false,
freeze: false
}

/**
* A KYC, freeze and wipe compliant fungible token, based on ERC20 that enables a token that has more control
* for regulatory and stricter requirements for investment purposes.
*
* @type {{decimals: number}}
*/
const CompliantFungible = {
...Fungible,
reference: "compliance.fungible",
kyc: true,
wipe: true,
freeze: true
}

/**
* A KYC, freeze and wipe compliant fungible token, based on ERC721/Non fungible that enables a token
* that has more control for regulatory and stricter use cases.
*
* @type {{decimals: number}}
*/
const CompliantNonFungible = {
...NonFungible,
reference: "compliance.nonfungible",
kyc: true,
wipe: true,
freeze: true
}

/**
* A special token that acts as a receipt for deposited assets in a pool. These
* can be returned to the treasury account that minted them. Upon receipt a claimant
* will be returned all tokens deposited and their fair share of the reward distribution.
*
* @type {{decimals: number}}
*/
const UnibarLiquidityProviderReceipt = {
...NonFungible,
reference: "lp.reward.receipt"
}

export default {
Fungible,
NonFungible,
CompliantFungible,
CompliantNonFungible,
UnibarLiquidityProviderReceipt
}
32 changes: 32 additions & 0 deletions app/validators/createTokenRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const Joi = require("@hapi/joi")

const TRILLION = 10 ** 12

const schema = Joi.object({
symbol: Joi.string()
.max(100)
.required(),
name: Joi.string()
.max(100)
.required(),
memo: Joi.string()
.max(100)
.optional(),
supply: Joi.number()
.positive()
.max(TRILLION)
.min(1)
.required(),
requires_kyc: Joi.bool().default(false),
can_freeze: Joi.bool().default(false)
}).options({ allowUnknown: true })

function createTokenRequest(candidate = {}) {
const validation = schema.validate(candidate || {})

if (validation.error) {
return validation.error.details.map(error => error.message)
}
}

export default createTokenRequest
Loading

0 comments on commit 152b0f4

Please sign in to comment.