From 92804f39bde44a8b3c625242b01902eb9a48dab3 Mon Sep 17 00:00:00 2001 From: Wingman4l7 Date: Tue, 12 Nov 2019 15:32:48 -0800 Subject: [PATCH] Adding Binance integration. (#10) * Fixed bug & added Binance support. --- CHANGELOG.md | 10 +- package.json | 2 +- src/interfaces/DragonchainClientInterfaces.ts | 32 ++++- .../DragonchainClient.spec.ts | 52 +++++++- .../dragonchain-client/DragonchainClient.ts | 125 +++++++++++++++++- 5 files changed, 212 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee270d0..beaa3f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,17 @@ # Changelog -## 4.0.1 +## 4.1.0 + +- **Feature:** + + - Add support for new Binance interchain endpoints with client functions: + - `createBinanceInterchain` + - `updateBinanceInterchain` + - `signBinanceTransaction` - **Bug Fix** - Remove erroneous `?` from query string when no query string parameters are provided + - Fix typing bug with `getSmartContractObject` ## 4.0.0 diff --git a/package.json b/package.json index 7faf722..01a238d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dragonchain-sdk", - "version": "4.0.1", + "version": "4.1.0", "description": "Dragonchain SDK for Node.JS and the Browser", "license": "Apache-2.0", "homepage": "https://github.com/dragonchain/dragonchain-sdk-javascript#readme", diff --git a/src/interfaces/DragonchainClientInterfaces.ts b/src/interfaces/DragonchainClientInterfaces.ts index 88c81bb..35aa5f3 100644 --- a/src/interfaces/DragonchainClientInterfaces.ts +++ b/src/interfaces/DragonchainClientInterfaces.ts @@ -221,7 +221,7 @@ export interface PublicBlockchainTransactionResponse { signed: string; } -export type SupportedInterchains = 'ethereum' | 'bitcoin'; +export type SupportedInterchains = 'ethereum' | 'bitcoin' | 'binance'; /** * @example @@ -255,7 +255,7 @@ export interface EthereumInterchainNetwork { * "blockchain": "bitcoin", * "name": "myBTCTestNetwork", * "rpc_address": "http://1.2.3.4:18332", - * "tesnet": true, + * "testnet": true, * "address": "n1bUzF6LRENLPaiJRFTcnGLMLsbZSquft1" * } * ``` @@ -269,6 +269,32 @@ export interface BitcoinInterchainNetwork { address: string; } +/** + * @example + * ```json + * + * { + * "version": "1", + * "blockchain": "binance", + * "name": "myBNBTestNetwork", + * "testnet": True, + * "node_url": "http://1.2.3.4", + * "rpc_port": 26657, + * "api_port": 11699, + * "address": "tbnb1zesqcktldshz7tat9u74duc037frzwvdq83wan" + * } + * ``` + */ +export interface BinanceInterchainNetwork { + version: string; + blockchain: 'binance'; + name: string; + node_url: string; + rpc_port: number; + api_port: number; + address: string; +} + /** * Array of interchains will be all of one type * @example @@ -287,7 +313,7 @@ export interface BitcoinInterchainNetwork { * ``` */ export interface InterchainNetworkList { - interchains: BitcoinInterchainNetwork[] | EthereumInterchainNetwork[]; + interchains: BitcoinInterchainNetwork[] | EthereumInterchainNetwork[] | BinanceInterchainNetwork[]; } /** diff --git a/src/services/dragonchain-client/DragonchainClient.spec.ts b/src/services/dragonchain-client/DragonchainClient.spec.ts index 8608182..bcbac31 100644 --- a/src/services/dragonchain-client/DragonchainClient.spec.ts +++ b/src/services/dragonchain-client/DragonchainClient.spec.ts @@ -425,6 +425,23 @@ describe('DragonchainClient', () => { }); }); + describe('.createBinanceInterchain', () => { + it('calls #fetch() with correct params', async () => { + const fakeBody: any = { + version: '1', + name: 'banana', + testnet: true, + private_key: 'abcd', + node_url: 'some IP', + rpc_port: 1234, + api_port: 5678 + }; + await client.createBinanceInterchain({ name: 'banana', testnet: true, privateKey: 'abcd', nodeURL: 'some IP', rpcPort: 1234, apiPort: 5678 }); + const obj = { ...expectedFetchOptions, body: JSON.stringify(fakeBody) }; + assert.calledWith(fetch, 'fakeUrl/v1/interchains/binance', obj); + }); + }); + describe('.signBitcoinTransaction', () => { it('calls #fetch() with correct params', async () => { const fakeBody: any = { @@ -457,6 +474,21 @@ describe('DragonchainClient', () => { }); }); + describe('.signBinanceTransaction', () => { + it('calls #fetch() with correct params', async () => { + const fakeBody: any = { + version: '1', + amount: 123, + to_address: 'receiver addy', + symbol: 'TOKEN', + memo: 'for bananas' + }; + await client.signBinanceTransaction({ name: 'banana', amount: 123, toAddress: 'receiver addy', symbol: 'TOKEN', memo: 'for bananas' }); + const obj = { ...expectedFetchOptions, body: JSON.stringify(fakeBody) }; + assert.calledWith(fetch, 'fakeUrl/v1/interchains/binance/banana/transaction', obj); + }); + }); + describe('.setDefaultInterchainNetwork', () => { it('calls #fetch() with correct params', async () => { const fakeBody: any = { @@ -546,14 +578,30 @@ describe('DragonchainClient', () => { it('calls #fetch() with correct params', async () => { const fakeBody: any = { version: '1', - private_key: 'private key', + private_key: 'abcd', rpc_address: 'some rpc', chain_id: 12 }; - await client.updateEthereumInterchain({ name: 'banana', privateKey: 'private key', rpcAddress: 'some rpc', chainId: 12 }); + await client.updateEthereumInterchain({ name: 'banana', privateKey: 'abcd', rpcAddress: 'some rpc', chainId: 12 }); const obj = { ...expectedFetchOptions, body: JSON.stringify(fakeBody) }; assert.calledWith(fetch, 'fakeUrl/v1/interchains/ethereum/banana', obj); }); }); + + describe('.updateBinanceInterchain', () => { + it('calls #fetch() with correct params', async () => { + const fakeBody: any = { + version: '1', + testnet: true, + private_key: 'abcd', + node_url: 'some IP', + rpc_port: 1234, + api_port: 5678 + }; + await client.updateBinanceInterchain({ name: 'banana', testnet: true, privateKey: 'abcd', nodeURL: 'some IP', rpcPort: 1234, apiPort: 5678 }); + const obj = { ...expectedFetchOptions, body: JSON.stringify(fakeBody) }; + assert.calledWith(fetch, 'fakeUrl/v1/interchains/binance/banana', obj); + }); + }); }); }); diff --git a/src/services/dragonchain-client/DragonchainClient.ts b/src/services/dragonchain-client/DragonchainClient.ts index d665e91..290fe12 100644 --- a/src/services/dragonchain-client/DragonchainClient.ts +++ b/src/services/dragonchain-client/DragonchainClient.ts @@ -48,6 +48,7 @@ import { DeleteAPIKeyResponse, EthereumInterchainNetwork, BitcoinInterchainNetwork, + BinanceInterchainNetwork, SupportedInterchains, InterchainNetworkList, CustomTextFieldOptions, @@ -748,8 +749,7 @@ export class DragonchainClient { if (!process.env.SMART_CONTRACT_ID) throw new FailureByDesign('PARAM_ERROR', 'Parameter `smartContractId` is required when not running within a smart contract'); options.smartContractId = process.env.SMART_CONTRACT_ID; } - const response = (await this.get(`/v1/get/${options.smartContractId}/${options.key}`, false)) as unknown; - return response as string; + return (await this.get(`/v1/get/${options.smartContractId}/${options.key}`, false)) as Response; }; /** @@ -1076,6 +1076,127 @@ export class DragonchainClient { return (await this.post(`/v1/interchains/ethereum/${options.name}/transaction`, body)) as Response; }; + /** + * Create (or overwrite) a binance wallet/network for interchain use + */ + + public createBinanceInterchain = async (options: { + /** + * The name of the network to update + */ + name: string; + /** + * Whether or not this is a testnet wallet/address. Defaults to True. + */ + testnet?: boolean; + /** + * The base64 or hex encoded private key (or mnemonic) to use. Will automatically generate a random one if not provided + */ + privateKey?: string; + /** + * The endpoint of the binance node to use (i.e. http://my.node.address) + */ + nodeURL?: string; + /** + * The port being used to hit the RPC endpoints (i.e. 27147) + */ + rpcPort?: number; + /** + * The port being used to hit the API endpoints (i.e. 1169) + */ + apiPort?: number; + }) => { + if (!options.name) throw new FailureByDesign('PARAM_ERROR', 'Parameter `name` is required'); + if (options.rpcPort && !Number.isInteger(options.rpcPort)) throw new FailureByDesign('PARAM_ERROR', 'Parameter `rpcPort` must be an integer'); + if (options.apiPort && !Number.isInteger(options.apiPort)) throw new FailureByDesign('PARAM_ERROR', 'Parameter `apiPort` must be an integer'); + const body: any = { version: '1', name: options.name }; + if (typeof options.testnet === 'boolean') body.testnet = options.testnet; + if (options.privateKey) body.private_key = options.privateKey; + if (options.nodeURL) body.node_url = options.nodeURL; + if (options.rpcPort) body.rpc_port = options.rpcPort; + if (options.rpcPort) body.api_port = options.apiPort; + return (await this.post(`/v1/interchains/binance`, body)) as Response; + }; + + /** + * Update an existing binance wallet/network for interchain use + */ + public updateBinanceInterchain = async (options: { + /** + * The name of the network to update + */ + name: string; + /** + * Whether or not this is a testnet wallet/address. Defaults to True. + */ + testnet?: boolean; + /** + * The base64 or hex encoded private key to use. Will automatically generate a random one if not provided + */ + privateKey?: string; + /** + * The endpoint of the binance node to use (i.e. http://my.node.address) + */ + nodeURL?: string; + /** + * The port being used to hit the RPC endpoints (i.e. 27147) + */ + rpcPort?: number; + /** + * The port being used to hit the API endpoints (i.e. 1169) + */ + apiPort?: number; + }) => { + if (!options.name) throw new FailureByDesign('PARAM_ERROR', 'Parameter `name` is required'); + if (options.rpcPort && !Number.isInteger(options.rpcPort)) throw new FailureByDesign('PARAM_ERROR', 'Parameter `rpcPort` must be an integer'); + if (options.apiPort && !Number.isInteger(options.apiPort)) throw new FailureByDesign('PARAM_ERROR', 'Parameter `apiPort` must be an integer'); + const body: any = { version: '1' }; + if (typeof options.testnet === 'boolean') body.testnet = options.testnet; + if (options.privateKey) body.private_key = options.privateKey; + if (options.nodeURL) body.node_url = options.nodeURL; + if (options.rpcPort) body.rpc_port = options.rpcPort; + if (options.apiPort) body.api_port = options.apiPort; + return (await this.patch(`/v1/interchains/binance/${options.name}`, body)) as Response; + }; + + /** + * Create and sign a binance transaction using your chain's interchain network + */ + public signBinanceTransaction = async (options: { + /** + * The name of the binance network to use for signing + */ + name: string; + /** + * the amount of token to send with this transaction + */ + amount: number; + /** + * The (hex-encoded) address to send the transaction to + */ + toAddress: string; + /** + * the exchange symbol for the token (defaults to BNB) + */ + symbol?: string; + /** + * string of data to publish in the transaction (defaults to "") + */ + memo?: string; + }) => { + if (!options.name) throw new FailureByDesign('PARAM_ERROR', 'Parameter `name` is required'); + if (!options.amount) throw new FailureByDesign('PARAM_ERROR', 'Parameter `amount` is required'); + if (!options.toAddress) throw new FailureByDesign('PARAM_ERROR', 'Parameter `toAddress` is required'); + const body: any = { + version: '1', + amount: options.amount, + to_address: options.toAddress + }; + if (options.symbol) body.symbol = options.symbol; + if (options.memo) body.memo = options.memo; + return (await this.post(`/v1/interchains/binance/${options.name}/transaction`, body)) as Response; + }; + /** * Get a configured interchain network/wallet from the chain */