-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: added createdToken rpc * feat: added create token req to rpcHandler * refactor: improved create token rpc request * feat: added loading trigger for create token * feat: sending loading finished trigger on send nano contract and create token * refactor: using CreateTokenTransaction
- Loading branch information
1 parent
31b3f85
commit 22b37a9
Showing
9 changed files
with
414 additions
and
5 deletions.
There are no files selected for viewing
170 changes: 170 additions & 0 deletions
170
packages/hathor-rpc-handler/__tests__/rpcMethods/createToken.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import { HathorWallet } from '@hathor/wallet-lib'; | ||
import { createToken } from '../../src/rpcMethods/createToken'; | ||
import { | ||
TriggerTypes, | ||
RpcMethods, | ||
CreateTokenRpcRequest, | ||
TriggerResponseTypes, | ||
RpcResponseTypes, | ||
} from '../../src/types'; | ||
import { CreateTokenError, PromptRejectedError } from '../../src/errors'; | ||
|
||
describe('createToken', () => { | ||
let rpcRequest: CreateTokenRpcRequest; | ||
let wallet: HathorWallet; | ||
let triggerHandler = jest.fn(); | ||
|
||
beforeEach(() => { | ||
rpcRequest = { | ||
method: RpcMethods.CreateToken, | ||
id: '1', | ||
jsonrpc: '2.0', | ||
params: { | ||
name: 'myToken', | ||
symbol: 'mtk', | ||
amount: 1000, | ||
address: 'address123', | ||
changeAddress: 'changeAddress123', | ||
createMint: true, | ||
mintAuthorityAddress: null, | ||
allowExternalMintAuthorityAddress: false, | ||
createMelt: true, | ||
meltAuthorityAddress: null, | ||
allowExternalMeltAuthorityAddress: false, | ||
data: null, | ||
pushTx: true, | ||
network: 'mainnet', | ||
}, | ||
} as unknown as CreateTokenRpcRequest; | ||
|
||
wallet = { | ||
isAddressMine: jest.fn(), | ||
createNewToken: jest.fn(), | ||
} as unknown as HathorWallet; | ||
|
||
triggerHandler = jest.fn(); | ||
}); | ||
|
||
it('should create a token successfully', async () => { | ||
const pinCode = '1234'; | ||
const transaction = { tx_id: 'transaction-id' }; | ||
const rpcResponse = { | ||
type: RpcResponseTypes.CreateTokenResponse, | ||
response: transaction, | ||
}; | ||
|
||
(wallet.isAddressMine as jest.Mock).mockResolvedValue(true); | ||
triggerHandler | ||
.mockResolvedValueOnce({ | ||
type: TriggerResponseTypes.CreateTokenConfirmationResponse, | ||
data: { | ||
accepted: true, | ||
}, | ||
}) | ||
.mockResolvedValueOnce({ | ||
type: TriggerResponseTypes.PinRequestResponse, | ||
data: { | ||
accepted: true, | ||
pinCode, | ||
}, | ||
}); | ||
|
||
(wallet.createNewToken as jest.Mock).mockResolvedValue(transaction); | ||
|
||
const result = await createToken(rpcRequest, wallet, {}, triggerHandler); | ||
|
||
expect(triggerHandler).toHaveBeenCalledTimes(2); | ||
expect(triggerHandler).toHaveBeenCalledWith( | ||
{ | ||
type: TriggerTypes.CreateTokenConfirmationPrompt, | ||
method: rpcRequest.method, | ||
data: rpcRequest.params, | ||
}, | ||
{} | ||
); | ||
expect(triggerHandler).toHaveBeenCalledWith( | ||
{ | ||
type: TriggerTypes.PinConfirmationPrompt, | ||
method: rpcRequest.method, | ||
}, | ||
{} | ||
); | ||
|
||
expect(wallet.createNewToken).toHaveBeenCalledWith( | ||
rpcRequest.params.name, | ||
rpcRequest.params.symbol, | ||
rpcRequest.params.amount, | ||
{ | ||
changeAddress: rpcRequest.params.change_address, | ||
address: rpcRequest.params.address, | ||
createMint: rpcRequest.params.create_mint, | ||
mintAuthorityAddress: rpcRequest.params.mint_authority_address, | ||
allowExternalMintAuthorityAddress: rpcRequest.params.allow_external_mint_authority_address, | ||
createMelt: rpcRequest.params.create_melt, | ||
meltAuthorityAddress: rpcRequest.params.melt_authority_address, | ||
allowExternalMeltAuthorityAddress: rpcRequest.params.allow_external_melt_authority_address, | ||
data: rpcRequest.params.data, | ||
pinCode, | ||
} | ||
); | ||
expect(result).toEqual(rpcResponse); | ||
}); | ||
|
||
it('should throw PromptRejectedError if the user rejects the confirmation prompt', async () => { | ||
(wallet.isAddressMine as jest.Mock).mockResolvedValue(true); | ||
|
||
triggerHandler.mockResolvedValueOnce({ | ||
type: TriggerResponseTypes.CreateTokenConfirmationResponse, | ||
data: { | ||
accepted: false, | ||
}, | ||
}); | ||
|
||
await expect(createToken(rpcRequest, wallet, {}, triggerHandler)).rejects.toThrow(PromptRejectedError); | ||
|
||
expect(triggerHandler).toHaveBeenCalledTimes(1); | ||
expect(triggerHandler).toHaveBeenCalledWith( | ||
{ | ||
type: TriggerTypes.CreateTokenConfirmationPrompt, | ||
method: rpcRequest.method, | ||
data: rpcRequest.params, | ||
}, | ||
{} | ||
); | ||
}); | ||
|
||
it('should throw CreateTokenError if the wallet transaction fails', async () => { | ||
const pinCode = '1234'; | ||
|
||
(wallet.isAddressMine as jest.Mock).mockResolvedValue(true); | ||
triggerHandler | ||
.mockResolvedValueOnce({ | ||
type: TriggerResponseTypes.CreateTokenConfirmationResponse, | ||
data: { | ||
accepted: true, | ||
}, | ||
}) | ||
.mockResolvedValueOnce({ | ||
type: TriggerResponseTypes.PinRequestResponse, | ||
data: { | ||
accepted: true, | ||
pinCode, | ||
}, | ||
}); | ||
|
||
(wallet.createNewToken as jest.Mock).mockRejectedValue(new Error('Transaction failed')); | ||
|
||
await expect(createToken(rpcRequest, wallet, {}, triggerHandler)).rejects.toThrow(CreateTokenError); | ||
|
||
expect(triggerHandler).toHaveBeenCalledTimes(2); | ||
}); | ||
|
||
it('should throw an error if the change address is not owned by the wallet', async () => { | ||
(wallet.isAddressMine as jest.Mock).mockResolvedValue(false); | ||
|
||
await expect(createToken(rpcRequest, wallet, {}, triggerHandler)).rejects.toThrow(Error); | ||
|
||
expect(wallet.isAddressMine).toHaveBeenCalledWith('changeAddress123'); | ||
}); | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
144 changes: 144 additions & 0 deletions
144
packages/hathor-rpc-handler/src/rpcMethods/createToken.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/** | ||
* Copyright (c) Hathor Labs and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import { HathorWallet, Transaction } from '@hathor/wallet-lib'; | ||
import { | ||
CreateTokenConfirmationPrompt, | ||
CreateTokenConfirmationResponse, | ||
CreateTokenLoadingFinishedTrigger, | ||
CreateTokenLoadingTrigger, | ||
CreateTokenRpcRequest, | ||
PinConfirmationPrompt, | ||
PinRequestResponse, | ||
RequestMetadata, | ||
RpcResponse, | ||
RpcResponseTypes, | ||
TriggerHandler, | ||
TriggerTypes, | ||
} from '../types'; | ||
import { CreateTokenError, PromptRejectedError } from '../errors'; | ||
|
||
/** | ||
* Handles the creation of a new token on the Hathor blockchain. | ||
* | ||
* This function orchestrates the entire token creation process, including | ||
* validating addresses, prompting for user confirmation and PIN code, and | ||
* interacting with the wallet library to create the token. It supports | ||
* optional parameters for mint and melt authorities, and allows for | ||
* customization of the token creation process. | ||
* | ||
* @param {CreateTokenRpcRequest} rpcRequest - The RPC request object containing the token details, including | ||
* the token name, symbol, amount, and various options related to minting and melting. | ||
* @param wallet - The wallet instance that will be used to create the token. | ||
* @param requestMetadata - Metadata associated with the request, such as the request ID | ||
* and other contextual information. | ||
* @param triggerHandler - A function that handles triggering user prompts, such as | ||
* confirmations and PIN entry. | ||
* | ||
* @returns An object containing transaction details of the created token. | ||
*/ | ||
export async function createToken( | ||
rpcRequest: CreateTokenRpcRequest, | ||
wallet: HathorWallet, | ||
requestMetadata: RequestMetadata, | ||
triggerHandler: TriggerHandler, | ||
) { | ||
const { name, symbol, amount } = rpcRequest.params; | ||
const address = rpcRequest.params.address || null; | ||
const changeAddress = rpcRequest.params.change_address || null; | ||
const createMint = rpcRequest.params.create_mint ?? true; | ||
const mintAuthorityAddress = rpcRequest.params.mint_authority_address || null; | ||
const allowExternalMintAuthorityAddress = rpcRequest.params.allow_external_mint_authority_address || false; | ||
const createMelt = rpcRequest.params.create_melt ?? true; | ||
const meltAuthorityAddress = rpcRequest.params.melt_authority_address || null; | ||
const allowExternalMeltAuthorityAddress = rpcRequest.params.allow_external_melt_authority_address || false; | ||
const data = rpcRequest.params.data || null; | ||
|
||
if (changeAddress && !await wallet.isAddressMine(changeAddress)) { | ||
throw new Error('Change address is not from this wallet'); | ||
} | ||
|
||
const pinPrompt: PinConfirmationPrompt = { | ||
type: TriggerTypes.PinConfirmationPrompt, | ||
method: rpcRequest.method, | ||
}; | ||
|
||
const createTokenPrompt: CreateTokenConfirmationPrompt = { | ||
type: TriggerTypes.CreateTokenConfirmationPrompt, | ||
method: rpcRequest.method, | ||
data: { | ||
name, | ||
symbol, | ||
amount, | ||
address, | ||
changeAddress, | ||
createMint, | ||
mintAuthorityAddress, | ||
allowExternalMintAuthorityAddress, | ||
createMelt, | ||
meltAuthorityAddress, | ||
allowExternalMeltAuthorityAddress, | ||
data, | ||
}, | ||
}; | ||
|
||
const createTokeResponse = await triggerHandler(createTokenPrompt, requestMetadata) as CreateTokenConfirmationResponse; | ||
|
||
if (!createTokeResponse.data.accepted) { | ||
throw new PromptRejectedError(); | ||
} | ||
|
||
const pinCodeResponse: PinRequestResponse = (await triggerHandler(pinPrompt, requestMetadata)) as PinRequestResponse; | ||
|
||
if (!pinCodeResponse.data.accepted) { | ||
throw new PromptRejectedError('Pin prompt rejected'); | ||
} | ||
|
||
try { | ||
const createTokenLoadingTrigger: CreateTokenLoadingTrigger = { | ||
type: TriggerTypes.CreateTokenLoadingTrigger, | ||
}; | ||
|
||
// No need to await as this is a fire-and-forget trigger | ||
triggerHandler(createTokenLoadingTrigger, requestMetadata); | ||
|
||
const response: Transaction = await wallet.createNewToken( | ||
name, | ||
symbol, | ||
amount, | ||
{ | ||
changeAddress, | ||
address, | ||
createMint, | ||
mintAuthorityAddress, | ||
allowExternalMintAuthorityAddress, | ||
createMelt, | ||
meltAuthorityAddress, | ||
allowExternalMeltAuthorityAddress, | ||
data, | ||
pinCode: pinCodeResponse.data.pinCode, | ||
} | ||
); | ||
|
||
const createTokenLoadingFinished: CreateTokenLoadingFinishedTrigger = { | ||
type: TriggerTypes.CreateTokenLoadingFinishedTrigger, | ||
}; | ||
triggerHandler(createTokenLoadingFinished, requestMetadata); | ||
|
||
return { | ||
type: RpcResponseTypes.CreateTokenResponse, | ||
response, | ||
} as RpcResponse; | ||
|
||
} catch (err) { | ||
if (err instanceof Error) { | ||
throw new CreateTokenError(err.message); | ||
} else { | ||
throw new CreateTokenError('An unknown error occurred'); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.