UNPKG

zumokit

Version:

ZumoKit is a Wallet as a Service SDK

825 lines (775 loc) 25.5 kB
import { Decimal } from 'decimal.js'; import { errorProxy } from './utility'; import { CurrencyCode, Network, AccountType, AccountJSON, AccountDataSnapshotJSON, Address, CardType, CardStatus, CardDetails, AuthenticationConfig, KbaAnswer, CustodyType, TradingPairJSON, } from './interfaces'; import { Wallet } from './Wallet'; import { ZumoKitError } from './ZumoKitError'; import { Account, AccountFiatProperties, AccountDataSnapshot, Card, ComposedTransaction, ComposedExchange, Transaction, Exchange, TradingPair, } from './models'; /** * User instance, obtained via {@link ZumoKit.signIn} method, provides methods for managing user wallet and accounts. * <p> * Refer to * <a href="https://developers.zumo.money/docs/guides/manage-user-wallet">Manage User Wallet</a>, * <a href="https://developers.zumo.money/docs/guides/create-fiat-account">Create Fiat Account</a>, * <a href="https://developers.zumo.money/docs/guides/view-user-accounts">View User Accounts</a> and * <a href="https://developers.zumo.money/docs/guides/get-account-data">Get Account Data</a> * guides for usage details. */ export class User { private zumoCoreModule: any; private userImpl: any; private accountDataListeners: Array< (state: Array<AccountDataSnapshot>) => void > = []; private accountDataListenersImpl: Array<any> = []; /** User identifier. */ id: string; /** User integrator identifier. */ integratorId: string; /** Indicator if user has wallet. */ hasWallet: boolean; /** User accounts. */ accounts: Array<Account>; /** @internal */ constructor(zumoCoreModule: any, userImpl: any) { this.zumoCoreModule = zumoCoreModule; this.userImpl = userImpl; this.id = userImpl.getId(); this.integratorId = userImpl.getIntegratorId(); this.hasWallet = userImpl.hasWallet(); this.accounts = JSON.parse(userImpl.getAccounts()).map( (json: AccountJSON) => new Account(json) ); this.addAccountDataListener((snapshots) => { this.accounts = snapshots.map((snapshot) => snapshot.account); }); } /** * Create user wallet seeded by provided mnemonic and encrypted with user's password. * <p> * Mnemonic can be generated by {@link Utils.generateMnemonic} utility method. * @param mnemonic mnemonic seed phrase * @param password user provided password */ createWallet(mnemonic: string, password: string) { const { zumoCoreModule } = this; return errorProxy<Wallet>(zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.createWallet( mnemonic, password, new zumoCoreModule.WalletCallbackWrapper({ onError: (error: string) => { reject(new ZumoKitError(error)); }, onSuccess: (wallet: any) => { this.hasWallet = true; resolve(new Wallet(zumoCoreModule, wallet)); }, }) ); }); } /** * Recover user wallet with mnemonic seed phrase corresponding to user's wallet. * This can be used if user forgets his password or wants to change his wallet password. * @param mnemonic mnemonic seed phrase corresponding to user's wallet * @param password user provided password */ recoverWallet(mnemonic: string, password: string) { const { zumoCoreModule } = this; return errorProxy<Wallet>(zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.recoverWallet( mnemonic, password, new zumoCoreModule.WalletCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(wallet: any) { resolve(new Wallet(zumoCoreModule, wallet)); }, }) ); }); } /** * Unlock user wallet with user's password. * @param password user provided password */ unlockWallet(password: string) { const { zumoCoreModule } = this; return errorProxy<Wallet>(zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.unlockWallet( password, new zumoCoreModule.WalletCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(wallet: any) { resolve(new Wallet(zumoCoreModule, wallet)); }, }) ); }); } /** * Reveal mnemonic seed phrase used to seed user wallet. * @param password user provided password */ revealMnemonic(password: string) { const { zumoCoreModule } = this; return errorProxy<string>(zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.revealMnemonic( password, new zumoCoreModule.MnemonicCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(mnemonic: string) { resolve(mnemonic); }, }) ); }); } /** * Check if mnemonic seed phrase corresponds to user's wallet. * This is useful for validating seed phrase before trying to recover wallet. * @param mnemonic mnemonic seed phrase */ isRecoveryMnemonic(mnemonic: string): boolean { try { return this.userImpl.isRecoveryMnemonic(mnemonic); } catch (exception) { throw new ZumoKitError(this.zumoCoreModule.getException(exception)); } } /** * Get account in specific currency, on specific network, with specific type. * @param currencyCode currency code, e.g. 'BTC', 'ETH' or 'GBP' * @param network network type, e.g. 'MAINNET', 'TESTNET' or 'RINKEBY' * @param type account type, e.g. 'STANDARD', 'COMPATIBILITY' or 'SEGWIT' * @param custodyType custody type, e.g. 'CUSTODY' or 'NON-CUSTODY' */ getAccount( currencyCode: CurrencyCode, network: Network, type: AccountType, custodyType: CustodyType ) { const account = this.userImpl.getAccount( currencyCode, network, type, custodyType ); if (account.hasValue()) { return new Account(JSON.parse(account.get())); } return null; } /** * Check if user is a registered fiat customer. */ isFiatCustomer(): boolean { return this.userImpl.isFiatCustomer(); } /** * Make user fiat customer by providing user's personal details. * @param firstName first name * @param middleName middle name or null * @param lastName last name * @param dateOfBirth date of birth in ISO 8601 format, e.g '2020-08-12' * @param email email * @param phone phone number * @param address home address */ makeFiatCustomer( firstName: string, middleName: string | null, lastName: string, dateOfBirth: string, email: string, phone: string, address: Address ) { return errorProxy<void>( this.zumoCoreModule, (resolve: any, reject: any) => { const optionalMiddleName = new this.zumoCoreModule.OptionalString(); if (middleName) optionalMiddleName.set(middleName); this.userImpl.makeFiatCustomer( firstName, optionalMiddleName, lastName, dateOfBirth, email, phone, JSON.stringify(address), new this.zumoCoreModule.SuccessCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess() { resolve(); }, }) ); } ); } /** * Create custody or fiat account for specified currency. When creating a fiat account, * user must already be fiat customer. * @param currencyCode country code in ISO 4217 format, e.g. 'GBP' */ createAccount(currencyCode: CurrencyCode) { return errorProxy<Account>( this.zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.createAccount( currencyCode, new this.zumoCoreModule.AccountCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(account: string) { resolve(new Account(JSON.parse(account))); }, }) ); } ); } /** * Get nominated account details for specified account if it exists. * Refer to * <a href="https://developers.zumo.money/docs/guides/send-transactions#bitcoin">Create Fiat Account</a> * for explanation about nominated account. * @param accountId {@link Account Account} identifier */ getNominatedAccountFiatProperties(accountId: string) { return errorProxy<AccountFiatProperties | null>( this.zumoCoreModule, (resolve: any) => { this.userImpl.getNominatedAccountFiatProperties( accountId, new this.zumoCoreModule.AccountFiatPropertiesCallbackWrapper({ onError() { resolve(null); }, onSuccess(accountFiatProperties: string) { resolve( new AccountFiatProperties(JSON.parse(accountFiatProperties)) ); }, }) ); } ); } /** * Fetch Strong Customer Authentication (SCA) config. */ fetchAuthenticationConfig() { return errorProxy<void>( this.zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.fetchAuthenticationConfig( new this.zumoCoreModule.AuthenticationConfigCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(authConfig: string) { resolve(JSON.parse(authConfig) as AuthenticationConfig); }, }) ); } ); } /** * Create card for a fiat account. * <p> * At least one Knowledge-Based Authentication (KBA) answers should be defined, * answers are limited to 256 characters and cannot be null or empty and only * one answer per question type should be provided. * @param fiatAccountId fiat {@link Account account} identifier * @param cardType 'VIRTUAL' or 'PHYSICAL' * @param mobileNumber card holder mobile number, starting with a '+', followed by the country code and then the mobile number, or null * @param knowledgeBase list of KBA answers */ createCard( fiatAccountId: string, cardType: CardType, mobileNumber: string, knowledgeBase: Array<KbaAnswer> ) { return errorProxy<void>( this.zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.createCard( fiatAccountId, cardType, mobileNumber, JSON.stringify(knowledgeBase), new this.zumoCoreModule.CardCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(card: string) { resolve(new Card(JSON.parse(card))); }, }) ); } ); } /** * Set card status to 'ACTIVE', 'BLOCKED' or 'CANCELLED'. * - To block card, set card status to 'BLOCKED'. * - To activate a physical card, set card status to 'ACTIVE' and provide PAN and CVC2 fields. * - To cancel a card, set card status to 'CANCELLED'. * - To unblock a card, set card status to 'ACTIVE.'. * @param cardId {@link Card card} identifier * @param cardStatus new card status * @param pan PAN when activating a physical card, null otherwise (defaults to null) * @param cvv2 CVV2 when activating a physical card, null otherwise (defaults to null) */ setCardStatus( cardId: string, cardStatus: CardStatus, pan: string | null = null, cvv2: string | null = null ) { return errorProxy<void>( this.zumoCoreModule, (resolve: any, reject: any) => { const optionalPan = new this.zumoCoreModule.OptionalString(); if (pan) optionalPan.set(pan); const optionalCvv2 = new this.zumoCoreModule.OptionalString(); if (cvv2) optionalCvv2.set(cvv2); this.userImpl.setCardStatus( cardId, cardStatus, optionalPan, optionalCvv2, new this.zumoCoreModule.SuccessCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess() { resolve(); }, }) ); } ); } /** * Reveals sensitive card details. * @param cardId card identifier */ revealCardDetails(cardId: string) { return errorProxy<void>( this.zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.revealCardDetails( cardId, new this.zumoCoreModule.CardDetailsCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(cardDetails: string) { resolve(JSON.parse(cardDetails) as CardDetails); }, }) ); } ); } /** * Reveal card PIN. * @param cardId {@link Card card} identifier */ revealPin(cardId: string) { return errorProxy<void>( this.zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.revealPin( cardId, new this.zumoCoreModule.PinCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(pin: number) { resolve(pin); }, }) ); } ); } /** * Unblock card PIN. * @param cardId {@link Card card} identifier */ unblockPin(cardId: string) { return errorProxy<void>( this.zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.unblockPin( cardId, new this.zumoCoreModule.SuccessCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess() { resolve(); }, }) ); } ); } /** * Add KBA answers to a card without SCA. * <p> * This endpoint is used to set Knowledge-Based Authentication (KBA) answers to * a card without Strong Customer Authentication (SCA). Once it is set SCA flag * on corresponding card is set to true. * <p> * At least one answer should be defined, answers are limited to 256 characters and * cannot be null or empty and only one answer per question type should be provided. * * @param cardId card id * @param knowledgeBase list of KBA answers */ setAuthentication(cardId: string, knowledgeBase: Array<KbaAnswer>) { return errorProxy<void>( this.zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.setAuthentication( cardId, JSON.stringify(knowledgeBase), new this.zumoCoreModule.SuccessCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess() { resolve(); }, }) ); } ); } /** * Listen to all account data changes. * * @param listener interface to listen to user changes */ addAccountDataListener( listener: (snapshots: Array<AccountDataSnapshot>) => void ) { const listenerImpl = new this.zumoCoreModule.AccountDataListenerWrapper({ onDataChange(snapshots: string) { listener( JSON.parse(snapshots).map( (json: AccountDataSnapshotJSON) => new AccountDataSnapshot(json) ) ); }, }); this.userImpl.addAccountDataListener(listenerImpl); this.accountDataListeners.push(listener); this.accountDataListenersImpl.push(listenerImpl); } /** * Remove listener to state changes. * * @param listener interface to listen to state changes */ removeAccountDataListener( listener: (snapshots: Array<AccountDataSnapshot>) => void ) { let index; // eslint-disable-next-line no-cond-assign while ((index = this.accountDataListeners.indexOf(listener)) !== -1) { this.accountDataListeners.splice(index, 1); this.userImpl.removeAccountDataListener( this.accountDataListenersImpl.splice(index, 1)[0] ); } } /** * Compose transaction between custody or fiat accounts in Zumo ecosystem. * Refer to <a href="https://developers.zumo.money/docs/guides/send-transactions#internal-transaction">Send Transactions</a> * guide for usage details. * * @param fromAccountId custody or fiat {@link Account Account} identifier * @param toAccountId custody or fiat {@link Account Account} identifier * @param amount amount in source account currency * @param sendMax send maximum possible funds to destination (defaults to false) */ composeTransaction( fromAccountId: string, toAccountId: string, amount: Decimal | null, sendMax = false ) { return errorProxy<ComposedTransaction>( this.zumoCoreModule, (resolve: any, reject: any) => { const amountOptional = new this.zumoCoreModule.OptionalDecimal(); if (amount) amountOptional.set( new this.zumoCoreModule.Decimal(amount.toString()) ); this.userImpl.composeTransaction( fromAccountId, toAccountId, amountOptional, sendMax, new this.zumoCoreModule.ComposeTransactionCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(composedTransaction: string) { resolve(new ComposedTransaction(JSON.parse(composedTransaction))); }, }) ); } ); } /** * Compose custody withdraw transaction from custody account. * Refer to <a href="https://developers.zumo.money/docs/guides/send-transactions#custody-withdraw-transaction">Send Transactions</a> * guide for usage details. * * @param fromAccountId custody or fiat {@link Account Account} identifier * @param destination destination address or non-custodial account identifier * @param amount amount in source account currency * @param sendMax send maximum possible funds to destination (defaults to false) */ composeCustodyWithdrawTransaction( fromAccountId: string, destination: string, amount: Decimal | null, sendMax = false ) { return errorProxy<ComposedTransaction>( this.zumoCoreModule, (resolve: any, reject: any) => { const amountOptional = new this.zumoCoreModule.OptionalDecimal(); if (amount) amountOptional.set( new this.zumoCoreModule.Decimal(amount.toString()) ); this.userImpl.composeCustodyWithdrawTransaction( fromAccountId, destination, amountOptional, sendMax, new this.zumoCoreModule.ComposeTransactionCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(composedTransaction: string) { resolve(new ComposedTransaction(JSON.parse(composedTransaction))); }, }) ); } ); } /** * Compose transaction from user fiat account to user's nominated account. * Refer to <a href="https://developers.zumo.money/docs/guides/send-transactions#nominated-transaction">Send Transactions</a> * guide for usage details. * * @param fromAccountId {@link Account Account} identifier * @param amount amount in source account currency * @param sendMax send maximum possible funds to destination (defaults to false) */ composeNominatedTransaction( fromAccountId: string, amount: Decimal | null, sendMax = false ) { return errorProxy<ComposedTransaction>( this.zumoCoreModule, (resolve: any, reject: any) => { const amountOptional = new this.zumoCoreModule.OptionalDecimal(); if (amount) amountOptional.set( new this.zumoCoreModule.Decimal(amount.toString()) ); this.userImpl.composeNominatedTransaction( fromAccountId, amountOptional, sendMax, new this.zumoCoreModule.ComposeTransactionCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(composedTransaction: string) { resolve(new ComposedTransaction(JSON.parse(composedTransaction))); }, }) ); } ); } /** * Submit a transaction asynchronously. * Refer to <a href="https://developers.zumo.money/docs/guides/send-transactions#submit-transaction">Send Transactions</a> * guide for usage details. * * @param composedTransaction Composed transaction retrieved as a result * of one of the compose transaction methods * @param toAccountId Debit account id override, only applicable to direct custody deposits. * In case no account id is specified senders custody account will be debited. * @param metadata Optional metadata that will be attached to transaction */ submitTransaction( composedTransaction: ComposedTransaction, toAccountId: string | null = null, metadata: any = null ) { const optionalToAccountId = new this.zumoCoreModule.OptionalString(); if (toAccountId) optionalToAccountId.set(toAccountId); return errorProxy<Transaction>( this.zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.submitTransaction( JSON.stringify(composedTransaction.json), optionalToAccountId, JSON.stringify(metadata), new this.zumoCoreModule.SubmitTransactionCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(transaction: string) { resolve(new Transaction(JSON.parse(transaction))); }, }) ); } ); } /** * Fetch trading pairs that are currently supported. */ fetchTradingPairs() { return errorProxy<void>( this.zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.fetchTradingPairs( new this.zumoCoreModule.StringifiedJsonCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(stringifiedJSON: string) { const tradingPairsJSON = JSON.parse( stringifiedJSON ) as TradingPairJSON[]; resolve(tradingPairsJSON.map((json) => new TradingPair(json))); }, }) ); } ); } /** * Compose exchange asynchronously. * Refer to <a href="https://developers.zumo.money/docs/guides/make-exchanges#compose-exchange">Make Exchanges</a> * guide for usage details. * * @param debitAccountId {@link Account Account} identifier * @param creditAccountId {@link Account Account} identifier * @param debitAmount amount to be debited from debit account * @param sendMax exchange maximum possible funds (defaults to false) */ composeExchange( debitAccountId: string, creditAccountId: string, debitAmount: Decimal | null, sendMax = false ) { return errorProxy<ComposedExchange>( this.zumoCoreModule, (resolve: any, reject: any) => { const amountOptional = new this.zumoCoreModule.OptionalDecimal(); if (debitAmount) amountOptional.set( new this.zumoCoreModule.Decimal(debitAmount.toString()) ); this.userImpl.composeExchange( debitAccountId, creditAccountId, amountOptional, sendMax, new this.zumoCoreModule.ComposeExchangeCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(composedExchange: string) { resolve(new ComposedExchange(JSON.parse(composedExchange))); }, }) ); } ); } /** * Submit an exchange asynchronously. * Refer to <a href="https://developers.zumo.money/docs/guides/make-exchanges#submit-exchange">Make Exchanges</a> * guide for usage details. * * @param composedExchange Composed exchange retrieved as the result * of {@link composeExchange} method */ submitExchange(composedExchange: ComposedExchange) { return errorProxy<Exchange>( this.zumoCoreModule, (resolve: any, reject: any) => { this.userImpl.submitExchange( JSON.stringify(composedExchange.json), new this.zumoCoreModule.SubmitExchangeCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(exchange: string) { resolve(new Exchange(JSON.parse(exchange))); }, }) ); } ); } }