Types cleanup + more API methods onto new request method (#857)

* major types cleanup, more formatted api methods onto new request method

- getPaymentChannel() now uses this.request()
- getSettings() now uses this.request()
- getLedger() now uses this.request()
- transaction types cleaned up a bit, but still some work left to do
This commit is contained in:
Fred K. Schott
2018-03-14 16:08:57 -07:00
committed by Elliot Lee
parent c175e3f58e
commit 187154a2b0
34 changed files with 520 additions and 442 deletions

View File

@@ -49,7 +49,9 @@ import {
AccountInfoRequest, AccountInfoResponse, AccountInfoRequest, AccountInfoResponse,
AccountLinesRequest, AccountLinesResponse, AccountLinesRequest, AccountLinesResponse,
BookOffersRequest, BookOffersResponse, BookOffersRequest, BookOffersResponse,
GatewayBalancesRequest, GatewayBalancesResponse GatewayBalancesRequest, GatewayBalancesResponse,
LedgerRequest, LedgerResponse,
LedgerEntryRequest, LedgerEntryResponse
} from './common/types/commands' } from './common/types/commands'
@@ -154,6 +156,10 @@ class RippleAPI extends EventEmitter {
Promise<BookOffersResponse> Promise<BookOffersResponse>
async _request(command: 'gateway_balances', params: GatewayBalancesRequest): async _request(command: 'gateway_balances', params: GatewayBalancesRequest):
Promise<GatewayBalancesResponse> Promise<GatewayBalancesResponse>
async _request(command: 'ledger', params: LedgerRequest):
Promise<LedgerResponse>
async _request(command: 'ledger_entry', params: LedgerEntryRequest):
Promise<LedgerEntryResponse>
async _request(command: string, params: any = {}) { async _request(command: string, params: any = {}) {
return this.connection.request({ return this.connection.request({
...params, ...params,

View File

@@ -1,4 +1,8 @@
import {AccountRoot, SignerList} from '../objects' import {
AccountRootLedgerEntry,
SignerListLedgerEntry,
QueueData
} from '../objects'
export interface AccountInfoRequest { export interface AccountInfoRequest {
account: string, account: string,
@@ -10,27 +14,10 @@ export interface AccountInfoRequest {
} }
export interface AccountInfoResponse { export interface AccountInfoResponse {
account_data: AccountRoot, account_data: AccountRootLedgerEntry,
signer_lists?: SignerList[], signer_lists?: SignerListLedgerEntry[],
ledger_current_index?: number, ledger_current_index?: number,
ledger_index?: number, ledger_index?: number,
queue_data?: QueueData, queue_data?: QueueData,
validated?: boolean validated?: boolean
} }
export interface QueueData {
txn_count: number,
auth_change_queued?: boolean,
lowest_sequence?: number,
highest_sequence?: number,
max_spend_drops_total?: string,
transactions?: TransactionData[]
}
export interface TransactionData {
auth_change?: boolean,
fee?: string,
fee_level?: string,
max_spend_drops?: string,
seq?: number
}

View File

@@ -1,7 +1,7 @@
import { import {
TakerRequestAmount, TakerRequestAmount,
OfferCreateTransaction, RippledAmount,
RippledAmount OfferCreateTransaction
} from '../objects' } from '../objects'
export interface BookOffersRequest { export interface BookOffersRequest {
@@ -15,15 +15,15 @@ export interface BookOffersRequest {
} }
export interface BookOffersResponse { export interface BookOffersResponse {
offers: OrderBookOffer[], offers: BookOffer[],
ledger_hash?: string, ledger_hash?: string,
ledger_current_index?: number, ledger_current_index?: number,
ledger_index?: number, ledger_index?: number,
marker?: any marker?: any
} }
export interface OrderBookOffer extends OfferCreateTransaction { export interface BookOffer extends OfferCreateTransaction {
quality?: number quality?: string
owner_funds?: string, owner_funds?: string,
taker_gets_funded?: RippledAmount, taker_gets_funded?: RippledAmount,
taker_pays_funded?: RippledAmount taker_pays_funded?: RippledAmount

View File

@@ -1,6 +1,5 @@
import {Amount} from '../objects' import {Amount} from '../objects'
export interface GatewayBalancesRequest { export interface GatewayBalancesRequest {
account: string, account: string,
strict?: boolean, strict?: boolean,

View File

@@ -3,3 +3,5 @@ export * from './account_lines'
export * from './account_offers' export * from './account_offers'
export * from './book_offers' export * from './book_offers'
export * from './gateway_balances' export * from './gateway_balances'
export * from './ledger'
export * from './ledger_entry'

View File

@@ -0,0 +1,20 @@
import {Ledger, QueueData} from '../objects'
export interface LedgerRequest {
ledger_hash?: string
ledger_index?: number | ('validated' | 'closed' | 'current')
full?: boolean
accounts?: boolean
transactions?: boolean
expand?: boolean
owner_funds?: boolean
binary?: boolean
queue?: boolean
}
export interface LedgerResponse {
ledger_index: number
ledger_hash: string
ledger: Ledger
queue_data?: QueueData
}

View File

@@ -0,0 +1,31 @@
import {LedgerEntry} from '../objects'
export interface LedgerEntryRequest {
ledger_hash?: string
ledger_index?: number | ('validated' | 'closed' | 'current')
index?: string,
account_root?: string,
directory?: string | {
sub_index?: number,
dir_root: string
} | {
sub_index?: number,
owner: string
},
offer?: string | {
account: string,
seq: number
},
ripple_state?: {
accounts: [string, string],
currency: string
},
binary?: boolean
}
export interface LedgerEntryResponse {
index: string,
ledger_index: number,
node_binary?: string,
node?: LedgerEntry,
}

View File

@@ -1,17 +0,0 @@
export interface AccountRoot {
LedgerEntryType: string,
Account: string,
Flags: number,
Sequence: number,
Balance: string,
OwnerCount: number,
PreviousTxnID: string,
PreviousTxnLgrSeq: number,
AccountTxnID?: string,
RegularKey?: string,
EmailHash?: string,
MessageKey?: string
TickSize?: number,
TransferRate?: number,
Domain?: string
}

View File

@@ -1,7 +1,11 @@
export * from './accounts'
export * from './adjustments' export * from './adjustments'
export * from './amounts' export * from './amounts'
export * from './ledger'
export * from './ledger_entries'
export * from './memos' export * from './memos'
export * from './orders'
export * from './queue_data'
export * from './settings'
export * from './signers' export * from './signers'
export * from './transactions' export * from './transactions'
export * from './trustlines' export * from './trustlines'

View File

@@ -0,0 +1,26 @@
export interface Ledger {
account_hash: string,
accounts?: any[],
close_time: number,
close_time_human: string,
close_time_resolution: number,
closed: boolean,
ledger_hash: string,
ledger_index: string,
parent_hash: string,
total_coins: string,
transaction_hash: string,
transactions: string[] | object[],
// @deprecated
seqNum?: string,
// @deprecated
totalCoins?: string,
// @deprecated
hash?: string,
// TODO: undocumented
close_flags?: number,
// TODO: undocumented
parent_close_time?: number,
// TODO: undocumented
accountState?: any
}

View File

@@ -0,0 +1,56 @@
import {SignerEntry} from './index'
export interface PayChannelLedgerEntry {
LedgerEntryType: 'PayChannel',
Sequence: number,
Account: string,
Amount: string,
Balance: string,
PublicKey: string,
Destination: string,
SettleDelay: number,
Expiration?: number,
CancelAfter?: number,
SourceTag?: number,
DestinationTag?: number,
OwnerNode: string,
PreviousTxnID: string,
PreviousTxnLgrSeq: number,
index: string
}
export interface AccountRootLedgerEntry {
LedgerEntryType: 'AccountRoot',
Account: string,
Flags: number,
Sequence: number,
Balance: string,
OwnerCount: number,
PreviousTxnID: string,
PreviousTxnLgrSeq: number,
AccountTxnID?: string,
RegularKey?: string,
EmailHash?: string,
MessageKey?: string
TickSize?: number,
TransferRate?: number,
Domain?: string
}
export interface SignerListLedgerEntry {
LedgerEntryType: 'SignerList',
OwnerNode: string,
SignerQuorum: number,
SignerEntries: SignerEntry[],
SignerListID: number,
PreviousTxnID: string,
PreviousTxnLgrSeq: number
}
// TODO: Add the other ledger entry types, then remove the `any` fallback
// see https://ripple.com/build/ledger-format/#ledger-object-types
export type LedgerEntry =
PayChannelLedgerEntry |
AccountRootLedgerEntry |
SignerListLedgerEntry |
any

View File

@@ -0,0 +1,17 @@
import {Amount} from './amounts'
import {Memo} from './memos'
export type FormattedOrderSpecification = {
direction: string,
quantity: Amount,
totalPrice: Amount,
immediateOrCancel?: boolean,
fillOrKill?: boolean,
expirationTime?: string,
orderToReplace?: number,
memos?: Memo[],
// If enabled, the offer will not consume offers that exactly match it, and
// instead becomes an Offer node in the ledger. It will still consume offers
// that cross it.
passive?: boolean
}

View File

@@ -0,0 +1,16 @@
export interface QueueTransaction {
auth_change: boolean,
fee: string,
fee_level: string,
max_spend_drops: string,
seq: number
}
export interface QueueData {
txn_count: number,
auth_change_queued?: boolean,
lowest_sequence?: number,
highest_sequence?: number,
max_spend_drops_total?: string,
transactions?: QueueTransaction[]
}

View File

@@ -0,0 +1,30 @@
import {Memo} from './memos'
export type WeightedSigner = {
address: string,
weight: number
}
export type Signers = {
threshold?: number,
weights: WeightedSigner[]
}
export type FormattedSettings = {
passwordSpent?: boolean,
requireDestinationTag?: boolean,
requireAuthorization?: boolean,
disallowIncomingXRP?: boolean,
disableMasterKey?: boolean,
enableTransactionIDTracking?: boolean,
noFreeze?: boolean,
globalFreeze?: boolean,
defaultRipple?: boolean,
emailHash?: string|null,
messageKey?: string,
domain?: string,
transferRate?: number|null,
regularKey?: string,
signers?: Signers,
memos?: Memo[]
}

View File

@@ -1,13 +1,3 @@
export interface SignerList {
LedgerEntryType: string,
OwnerNode: string,
SignerQuorum: number,
SignerEntries: SignerEntry[],
SignerListID: number,
PreviousTxnID: string,
PreviousTxnLgrSeq: number
}
export interface SignerEntry { export interface SignerEntry {
Account: string, Account: string,
SignerWeight: number SignerWeight: number

View File

@@ -1,28 +1,29 @@
import {validate} from '../common' import {validate} from '../common'
import parseLedger from './parse/ledger' import {FormattedLedger, parseLedger} from './parse/ledger'
import {GetLedger} from './types' import {RippleAPI} from '../api'
export type LedgerOptions = { export type GetLedgerOptions = {
ledgerVersion?: number, ledgerVersion?: number,
includeAllData?: boolean, includeAllData?: boolean,
includeTransactions?: boolean, includeTransactions?: boolean,
includeState?: boolean includeState?: boolean
} }
async function getLedger(
function getLedger(options: LedgerOptions = {}): Promise<GetLedger> { this: RippleAPI, options: GetLedgerOptions = {}
): Promise<FormattedLedger> {
// 1. Validate
validate.getLedger({options}) validate.getLedger({options})
// 2. Make Request
const request = { const response = await this._request('ledger', {
command: 'ledger',
ledger_index: options.ledgerVersion || 'validated', ledger_index: options.ledgerVersion || 'validated',
expand: options.includeAllData, expand: options.includeAllData,
transactions: options.includeTransactions, transactions: options.includeTransactions,
accounts: options.includeState accounts: options.includeState
} })
// 3. Return Formatted Response
return this.connection.request(request).then(response => return parseLedger(response.ledger)
parseLedger(response.ledger))
} }
export default getLedger export default getLedger

View File

@@ -1,51 +1,28 @@
import * as _ from 'lodash' import * as _ from 'lodash'
import * as utils from './utils' import * as utils from './utils'
import parseOrderbookOrder from './parse/orderbook-order' import {
parseOrderbookOrder,
FormattedOrderbookOrder
} from './parse/orderbook-order'
import {validate} from '../common' import {validate} from '../common'
import {OrderSpecification} from './types'
import {Amount, Issue} from '../common/types/objects' import {Amount, Issue} from '../common/types/objects'
import {BookOffer} from '../common/types/commands'
import {RippleAPI} from '../api' import {RippleAPI} from '../api'
import {OfferCreateTransaction} from '../common/types/objects'
export type OrdersOptions = { export type FormattedOrderbook = {
limit?: number, bids: FormattedOrderbookOrder[],
ledgerVersion?: number asks: FormattedOrderbookOrder[]
}
export type Orderbook = {
base: Issue,
counter: Issue
}
export type OrderbookItem = {
specification: OrderSpecification,
properties: {
maker: string,
sequence: number,
makerExchangeRate: string
},
state?: {
fundedAmount: Amount,
priceOfFundedAmount: Amount
}
}
export type OrderbookOrders = Array<OrderbookItem>
export type GetOrderbook = {
bids: OrderbookOrders,
asks: OrderbookOrders
} }
function isSameIssue(a: Amount, b: Amount) { function isSameIssue(a: Amount, b: Amount) {
return a.currency === b.currency && a.counterparty === b.counterparty return a.currency === b.currency && a.counterparty === b.counterparty
} }
function directionFilter(direction: string, order: OrderbookItem) { function directionFilter(direction: string, order: FormattedOrderbookOrder) {
return order.specification.direction === direction return order.specification.direction === direction
} }
function flipOrder(order: OrderbookItem) { function flipOrder(order: FormattedOrderbookOrder) {
const specification = order.specification const specification = order.specification
const flippedSpecification = { const flippedSpecification = {
quantity: specification.totalPrice, quantity: specification.totalPrice,
@@ -56,13 +33,13 @@ function flipOrder(order: OrderbookItem) {
return _.merge({}, order, {specification: newSpecification}) return _.merge({}, order, {specification: newSpecification})
} }
function alignOrder(base: Amount, order: OrderbookItem) { function alignOrder(base: Amount, order: FormattedOrderbookOrder) {
const quantity = order.specification.quantity const quantity = order.specification.quantity
return isSameIssue(quantity, base) ? order : flipOrder(order) return isSameIssue(quantity, base) ? order : flipOrder(order)
} }
function formatBidsAndAsks( function formatBidsAndAsks(
orderbook: Orderbook, offers: OfferCreateTransaction[]) { orderbook: OrderbookInfo, offers: BookOffer[]) {
// the "base" currency is the currency that you are buying or selling // the "base" currency is the currency that you are buying or selling
// the "counter" is the currency that the "base" is priced in // the "counter" is the currency that the "base" is priced in
// a "bid"/"ask" is an order to buy/sell the base, respectively // a "bid"/"ask" is an order to buy/sell the base, respectively
@@ -83,7 +60,7 @@ function formatBidsAndAsks(
// account is to specify a "perspective", which affects which unfunded offers // account is to specify a "perspective", which affects which unfunded offers
// are returned // are returned
async function makeRequest( async function makeRequest(
api: RippleAPI, taker: string, options: OrdersOptions, api: RippleAPI, taker: string, options: GetOrderbookOptions,
takerGets: Issue, takerPays: Issue takerGets: Issue, takerPays: Issue
) { ) {
const orderData = utils.renameCounterpartyToIssuerInOrder({ const orderData = utils.renameCounterpartyToIssuerInOrder({
@@ -99,12 +76,23 @@ async function makeRequest(
}) })
} }
export type GetOrderbookOptions = {
limit?: number,
ledgerVersion?: number
}
export type OrderbookInfo = {
base: Issue,
counter: Issue
}
export default async function getOrderbook( export default async function getOrderbook(
this: RippleAPI, this: RippleAPI,
address: string, address: string,
orderbook: Orderbook, orderbook: OrderbookInfo,
options: OrdersOptions = {} options: GetOrderbookOptions = {}
): Promise<GetOrderbook> { ): Promise<FormattedOrderbook> {
// 1. Validate // 1. Validate
validate.getOrderbook({address, orderbook, options}) validate.getOrderbook({address, orderbook, options})
// 2. Make Request // 2. Make Request

View File

@@ -1,9 +1,8 @@
import * as _ from 'lodash' import * as _ from 'lodash'
import {validate} from '../common' import {validate} from '../common'
import parseAccountOrder from './parse/account-order' import {FormattedAccountOrder, parseAccountOrder} from './parse/account-order'
import {Order} from './types'
import {RippleAPI} from '../api' import {RippleAPI} from '../api'
import {AccountOffersResponse} from '../common/types/commands/account_offers' import {AccountOffersResponse} from '../common/types/commands'
export type GetOrdersOptions = { export type GetOrdersOptions = {
limit?: number, limit?: number,
@@ -12,8 +11,8 @@ export type GetOrdersOptions = {
function formatResponse( function formatResponse(
address: string, responses: AccountOffersResponse[] address: string, responses: AccountOffersResponse[]
): Order[] { ): FormattedAccountOrder[] {
let orders: Order[] = [] let orders: FormattedAccountOrder[] = []
for (const response of responses) { for (const response of responses) {
const offers = response.offers.map(offer => { const offers = response.offers.map(offer => {
return parseAccountOrder(address, offer) return parseAccountOrder(address, offer)
@@ -25,7 +24,7 @@ function formatResponse(
export default async function getOrders( export default async function getOrders(
this: RippleAPI, address: string, options: GetOrdersOptions = {} this: RippleAPI, address: string, options: GetOrdersOptions = {}
): Promise<Order[]> { ): Promise<FormattedAccountOrder[]> {
// 1. Validate // 1. Validate
validate.getOrders({address, options}) validate.getOrders({address, options})
// 2. Make Request // 2. Make Request

View File

@@ -3,7 +3,16 @@ import parseAmount from './amount'
import {parseTimestamp, adjustQualityForXRP} from './utils' import {parseTimestamp, adjustQualityForXRP} from './utils'
import {removeUndefined} from '../../common' import {removeUndefined} from '../../common'
import {orderFlags} from './flags' import {orderFlags} from './flags'
import {Order} from '../types' import {FormattedOrderSpecification} from '../../common/types/objects'
export type FormattedAccountOrder = {
specification: FormattedOrderSpecification,
properties: {
maker: string,
sequence: number,
makerExchangeRate: string
}
}
// TODO: remove this function once rippled provides quality directly // TODO: remove this function once rippled provides quality directly
function computeQuality(takerGets, takerPays) { function computeQuality(takerGets, takerPays) {
@@ -13,7 +22,9 @@ function computeQuality(takerGets, takerPays) {
// rippled 'account_offers' returns a different format for orders than 'tx' // rippled 'account_offers' returns a different format for orders than 'tx'
// the flags are also different // the flags are also different
function parseAccountOrder(address: string, order: any): Order { export function parseAccountOrder(
address: string, order: any
): FormattedAccountOrder {
const direction = (order.flags & orderFlags.Sell) === 0 ? 'buy' : 'sell' const direction = (order.flags & orderFlags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(order.taker_gets) const takerGetsAmount = parseAmount(order.taker_gets)
const takerPaysAmount = parseAmount(order.taker_pays) const takerPaysAmount = parseAmount(order.taker_pays)
@@ -43,5 +54,3 @@ function parseAccountOrder(address: string, order: any): Order {
return {specification, properties} return {specification, properties}
} }
export default parseAccountOrder

View File

@@ -1,7 +1,28 @@
import * as _ from 'lodash' import * as _ from 'lodash'
import {removeUndefined, rippleTimeToISO8601} from '../../common' import {removeUndefined, rippleTimeToISO8601} from '../../common'
import parseTransaction from './transaction' import parseTransaction from './transaction'
import {GetLedger} from '../types' import {Ledger} from '../../common/types/objects'
export type FormattedLedger = {
// TODO: properties in type don't match response object. Fix!
// accepted: boolean,
// closed: boolean,
stateHash: string,
closeTime: string,
closeTimeResolution: number,
closeFlags: number,
ledgerHash: string,
ledgerVersion: number,
parentLedgerHash: string,
parentCloseTime: string,
totalDrops: string,
transactionHash: string,
transactions?: Array<Object>,
rawTransactions?: string,
transactionHashes?: Array<string>,
rawState?: string,
stateHashes?: Array<string>
}
function parseTransactionWrapper(ledgerVersion, tx) { function parseTransactionWrapper(ledgerVersion, tx) {
const transaction = _.assign({}, _.omit(tx, 'metaData'), { const transaction = _.assign({}, _.omit(tx, 'metaData'), {
@@ -39,7 +60,7 @@ function parseState(state) {
return {rawState: JSON.stringify(state)} return {rawState: JSON.stringify(state)}
} }
function parseLedger(ledger: any): GetLedger { export function parseLedger(ledger: Ledger): FormattedLedger {
const ledgerVersion = parseInt(ledger.ledger_index || ledger.seqNum, 10) const ledgerVersion = parseInt(ledger.ledger_index || ledger.seqNum, 10)
return removeUndefined(Object.assign({ return removeUndefined(Object.assign({
stateHash: ledger.account_hash, stateHash: ledger.account_hash,
@@ -57,5 +78,3 @@ function parseLedger(ledger: any): GetLedger {
parseState(ledger.accountState) parseState(ledger.accountState)
)) ))
} }
export default parseLedger

View File

@@ -2,9 +2,14 @@ import * as assert from 'assert'
import {parseTimestamp} from './utils' import {parseTimestamp} from './utils'
import parseAmount from './amount' import parseAmount from './amount'
import {removeUndefined, txFlags} from '../../common' import {removeUndefined, txFlags} from '../../common'
import {
FormattedOrderSpecification,
OfferCreateTransaction
} from '../../common/types/objects/index'
const flags = txFlags.OfferCreate const flags = txFlags.OfferCreate
function parseOrder(tx: any): Object { function parseOrder(tx: OfferCreateTransaction): FormattedOrderSpecification {
assert(tx.TransactionType === 'OfferCreate') assert(tx.TransactionType === 'OfferCreate')
const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell' const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell'

View File

@@ -4,8 +4,25 @@ import {removeUndefined} from '../../common'
import {orderFlags} from './flags' import {orderFlags} from './flags'
import parseAmount from './amount' import parseAmount from './amount'
import {BookOffer} from '../../common/types/commands'
import {Amount, FormattedOrderSpecification} from '../../common/types/objects'
function parseOrderbookOrder(order: any): Object { export type FormattedOrderbookOrder = {
specification: FormattedOrderSpecification,
properties: {
maker: string,
sequence: number,
makerExchangeRate: string
},
state?: {
fundedAmount: Amount,
priceOfFundedAmount: Amount
}
}
export function parseOrderbookOrder(
order: BookOffer
): FormattedOrderbookOrder {
const direction = (order.Flags & orderFlags.Sell) === 0 ? 'buy' : 'sell' const direction = (order.Flags & orderFlags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(order.TakerGets) const takerGetsAmount = parseAmount(order.TakerGets)
const takerPaysAmount = parseAmount(order.TakerPays) const takerPaysAmount = parseAmount(order.TakerPays)
@@ -14,7 +31,7 @@ function parseOrderbookOrder(order: any): Object {
// note: immediateOrCancel and fillOrKill orders cannot enter the order book // note: immediateOrCancel and fillOrKill orders cannot enter the order book
// so we can omit those flags here // so we can omit those flags here
const specification = removeUndefined({ const specification: FormattedOrderSpecification = removeUndefined({
direction: direction, direction: direction,
quantity: quantity, quantity: quantity,
totalPrice: totalPrice, totalPrice: totalPrice,
@@ -40,5 +57,3 @@ function parseOrderbookOrder(order: any): Object {
const state = _.isEmpty(available) ? undefined : available const state = _.isEmpty(available) ? undefined : available
return removeUndefined({specification, properties, state}) return removeUndefined({specification, properties, state})
} }
export default parseOrderbookOrder

View File

@@ -1,35 +1,8 @@
import {parseTimestamp} from './utils' import {parseTimestamp} from './utils'
import {removeUndefined, dropsToXrp} from '../../common' import {removeUndefined, dropsToXrp} from '../../common'
import {PayChannelLedgerEntry} from '../../common/types/objects'
export type FormattedPaymentChannel = {
export type PaymentChannel = {
Sequence: number,
Account: string,
Amount: string,
Balance: string,
PublicKey: string,
Destination: string,
SettleDelay: number,
Expiration?: number,
CancelAfter?: number,
SourceTag?: number,
DestinationTag?: number,
OwnerNode: string,
LedgerEntryType: string,
PreviousTxnID: string,
PreviousTxnLgrSeq: number,
index: string
}
export type LedgerEntryResponse = {
node: PaymentChannel,
ledger_current_index?: number,
ledger_hash?: string,
ledger_index: number,
validated: boolean
}
export type PaymentChannelResponse = {
account: string, account: string,
balance: string, balance: string,
publicKey: string, publicKey: string,
@@ -43,7 +16,9 @@ export type PaymentChannelResponse = {
previousAffectingTransactionLedgerVersion: number previousAffectingTransactionLedgerVersion: number
} }
function parsePaymentChannel(data: PaymentChannel): PaymentChannelResponse { export function parsePaymentChannel(
data: PayChannelLedgerEntry
): FormattedPaymentChannel {
return removeUndefined({ return removeUndefined({
account: data.Account, account: data.Account,
amount: dropsToXrp(data.Amount), amount: dropsToXrp(data.Amount),
@@ -59,5 +34,3 @@ function parsePaymentChannel(data: PaymentChannel): PaymentChannelResponse {
previousAffectingTransactionLedgerVersion: data.PreviousTxnLgrSeq previousAffectingTransactionLedgerVersion: data.PreviousTxnLgrSeq
}) })
} }
export default parsePaymentChannel

View File

@@ -1,29 +1,35 @@
import parsePaymentChannel, { import {
LedgerEntryResponse, PaymentChannel parsePaymentChannel,
FormattedPaymentChannel
} from './parse/payment-channel' } from './parse/payment-channel'
import {validate, errors} from '../common' import {validate, errors} from '../common'
import {RippleAPI} from '../api'
import {LedgerEntryResponse} from '../common/types/commands'
const NotFoundError = errors.NotFoundError const NotFoundError = errors.NotFoundError
function formatResponse(response: LedgerEntryResponse) { function formatResponse(
if (response.node !== undefined && response: LedgerEntryResponse
response.node.LedgerEntryType === 'PayChannel') { ): FormattedPaymentChannel {
return parsePaymentChannel(response.node) if (response.node === undefined ||
} else { response.node.LedgerEntryType !== 'PayChannel') {
throw new NotFoundError('Payment channel ledger entry not found') throw new NotFoundError('Payment channel ledger entry not found')
} }
return parsePaymentChannel(response.node)
} }
function getPaymentChannel(id: string): Promise<PaymentChannel> { async function getPaymentChannel(
this: RippleAPI, id: string
): Promise<FormattedPaymentChannel> {
// 1. Validate
validate.getPaymentChannel({id}) validate.getPaymentChannel({id})
// 2. Make Request
const request = { const response = await this._request('ledger_entry', {
command: 'ledger_entry',
index: id, index: id,
binary: false, binary: false,
ledger_index: 'validated' ledger_index: 'validated'
} })
// 3. Return Formatted Response
return this.connection.request(request).then(formatResponse) return formatResponse(response)
} }
export default getPaymentChannel export default getPaymentChannel

View File

@@ -1,31 +1,15 @@
import * as _ from 'lodash' import * as _ from 'lodash'
import parseFields from './parse/fields' import parseFields from './parse/fields'
import {validate, constants} from '../common' import {validate, constants} from '../common'
import {FormattedSettings} from '../common/types/objects'
import {AccountInfoResponse} from '../common/types/commands'
import {RippleAPI} from '../api'
const AccountFlags = constants.AccountFlags const AccountFlags = constants.AccountFlags
export type SettingsOptions = { export type SettingsOptions = {
ledgerVersion?: number ledgerVersion?: number
} }
export type GetSettings = {
passwordSpent?: boolean,
requireDestinationTag?: boolean,
requireAuthorization?: boolean,
depositAuthorization?: boolean,
disallowIncomingXRP?: boolean,
disableMasterKey?: boolean,
enableTransactionIDTracking?: boolean,
noFreeze?: boolean,
globalFreeze?: boolean,
defaultRipple?: boolean,
emailHash?: string|null,
messageKey?: string,
domain?: string,
transferRate?: number|null,
regularKey?: string
}
function parseFlags(value) { function parseFlags(value) {
const settings = {} const settings = {}
for (const flagName in AccountFlags) { for (const flagName in AccountFlags) {
@@ -36,25 +20,26 @@ function parseFlags(value) {
return settings return settings
} }
function formatSettings(response) { function formatSettings(response: AccountInfoResponse) {
const data = response.account_data const data = response.account_data
const parsedFlags = parseFlags(data.Flags) const parsedFlags = parseFlags(data.Flags)
const parsedFields = parseFields(data) const parsedFields = parseFields(data)
return _.assign({}, parsedFlags, parsedFields) return _.assign({}, parsedFlags, parsedFields)
} }
function getSettings(address: string, options: SettingsOptions = {} async function getSettings(
): Promise<GetSettings> { this: RippleAPI, address: string, options: SettingsOptions = {}
): Promise<FormattedSettings> {
// 1. Validate
validate.getSettings({address, options}) validate.getSettings({address, options})
// 2. Make Request
const request = { const response = await this._request('account_info', {
command: 'account_info',
account: address, account: address,
ledger_index: options.ledgerVersion || 'validated', ledger_index: options.ledgerVersion || 'validated',
signer_lists: true signer_lists: true
} })
// 3. Return Formatted Response
return this.connection.request(request).then(formatSettings) return formatSettings(response)
} }
export default getSettings export default getSettings

View File

@@ -1,144 +0,0 @@
import {Amount, Memo} from '../common/types/objects'
export type Outcome = {
result: string,
ledgerVersion: number,
indexInLedger: number,
fee: string,
balanceChanges: {
[key: string]: [{
currency: string,
counterparty?: string,
value: string
}]
},
orderbookChanges: Object,
timestamp?: string
}
export type Adjustment = {
address: string,
amount: {
currency: string,
counterparty?: string,
value: string
},
tag?: number
}
export type Trustline = {
currency: string,
counterparty: string,
limit: string,
qualityIn?: number,
qualityOut?: number,
ripplingDisabled?: boolean,
authorized?: boolean,
frozen?: boolean
}
export type Settings = {
passwordSpent?: boolean,
requireDestinationTag?: boolean,
requireAuthorization?: boolean,
depositAuthorization?: boolean,
disallowIncomingXRP?: boolean,
disableMasterKey?: boolean,
enableTransactionIDTracking?: boolean,
noFreeze?: boolean,
globalFreeze?: boolean,
defaultRipple?: boolean,
emailHash?: string,
messageKey?: string,
domain?: string,
transferRate?: number,
regularKey?: string
}
export type OrderCancellation = {
orderSequence: number
}
export type Payment = {
source: Adjustment,
destination: Adjustment,
paths?: string,
memos?: Array<Memo>,
invoiceID?: string,
allowPartialPayment?: boolean,
noDirectRipple?: boolean,
limitQuality?: boolean
}
export type PaymentTransaction = {
type: string,
specification: Payment,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type Order = {
direction: string,
quantity: Amount,
totalPrice: Amount,
immediateOrCancel?: boolean,
fillOrKill?: boolean,
passive?: boolean,
expirationTime?: string,
orderToReplace?: number,
memos?: Memo[]
}
export type OrderTransaction = {
type: string,
specification: Order,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type OrderCancellationTransaction = {
type: string,
specification: OrderCancellation,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type TrustlineTransaction = {
type: string,
specification: Trustline,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type SettingsTransaction = {
type: string,
specification: Settings,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type TransactionOptions = {
minLedgerVersion?: number,
maxLedgerVersion?: number
}
export type TransactionType = PaymentTransaction | OrderTransaction |
OrderCancellationTransaction | TrustlineTransaction | SettingsTransaction
export type TransactionResponse = TransactionType & {
hash: string,
ledger_index: number,
meta: any,
validated?: boolean
}

View File

@@ -3,12 +3,22 @@ import * as utils from './utils'
import parseTransaction from './parse/transaction' import parseTransaction from './parse/transaction'
import {validate, errors} from '../common' import {validate, errors} from '../common'
import {Connection} from '../common' import {Connection} from '../common'
import { import {FormattedTransactionType} from '../transaction/types'
TransactionType, TransactionResponse, TransactionOptions
} from './transaction-types' export type TransactionOptions = {
minLedgerVersion?: number,
maxLedgerVersion?: number
}
type TransactionResponse = FormattedTransactionType & {
hash: string,
ledger_index: number,
meta: any,
validated?: boolean
}
function attachTransactionDate(connection: Connection, tx: any function attachTransactionDate(connection: Connection, tx: any
): Promise<TransactionType> { ): Promise<FormattedTransactionType> {
if (tx.date) { if (tx.date) {
return Promise.resolve(tx) return Promise.resolve(tx)
} }
@@ -71,7 +81,7 @@ function convertError(connection: Connection, options: TransactionOptions,
} }
function formatResponse(options: TransactionOptions, tx: TransactionResponse function formatResponse(options: TransactionOptions, tx: TransactionResponse
): TransactionType { ): FormattedTransactionType {
if (tx.validated !== true || !isTransactionInRange(tx, options)) { if (tx.validated !== true || !isTransactionInRange(tx, options)) {
throw new errors.NotFoundError('Transaction not found') throw new errors.NotFoundError('Transaction not found')
} }
@@ -79,7 +89,7 @@ function formatResponse(options: TransactionOptions, tx: TransactionResponse
} }
function getTransaction(id: string, options: TransactionOptions = {} function getTransaction(id: string, options: TransactionOptions = {}
): Promise<TransactionType> { ): Promise<FormattedTransactionType> {
validate.getTransaction({id, options}) validate.getTransaction({id, options})
const request = { const request = {

View File

@@ -4,9 +4,8 @@ const {computeTransactionHash} = require('ripple-hashes')
import * as utils from './utils' import * as utils from './utils'
import parseTransaction from './parse/transaction' import parseTransaction from './parse/transaction'
import getTransaction from './transaction' import getTransaction from './transaction'
import {validate, errors} from '../common' import {validate, errors, Connection} from '../common'
import {Connection} from '../common' import {FormattedTransactionType} from '../transaction/types'
import {TransactionType} from './transaction-types'
export type TransactionsOptions = { export type TransactionsOptions = {
@@ -20,10 +19,10 @@ export type TransactionsOptions = {
counterparty?: string, counterparty?: string,
types?: Array<string>, types?: Array<string>,
binary?: boolean, binary?: boolean,
startTx?: TransactionType startTx?: FormattedTransactionType
} }
export type GetTransactionsResponse = Array<TransactionType> export type GetTransactionsResponse = Array<FormattedTransactionType>
function parseBinaryTransaction(transaction) { function parseBinaryTransaction(transaction) {
const tx = binary.decode(transaction.tx_blob) const tx = binary.decode(transaction.tx_blob)
@@ -43,7 +42,7 @@ function parseAccountTxTransaction(tx) {
{meta: _tx.meta, validated: _tx.validated})) {meta: _tx.meta, validated: _tx.validated}))
} }
function counterpartyFilter(filters, tx: TransactionType) { function counterpartyFilter(filters, tx: FormattedTransactionType) {
if (tx.address === filters.counterparty) { if (tx.address === filters.counterparty) {
return true return true
} }
@@ -57,7 +56,7 @@ function counterpartyFilter(filters, tx: TransactionType) {
} }
function transactionFilter(address: string, filters: TransactionsOptions, function transactionFilter(address: string, filters: TransactionsOptions,
tx: TransactionType tx: FormattedTransactionType
) { ) {
if (filters.excludeFailures && tx.outcome.result !== 'tesSUCCESS') { if (filters.excludeFailures && tx.outcome.result !== 'tesSUCCESS') {
return false return false
@@ -77,7 +76,9 @@ function transactionFilter(address: string, filters: TransactionsOptions,
return true return true
} }
function orderFilter(options: TransactionsOptions, tx: TransactionType) { function orderFilter(
options: TransactionsOptions, tx: FormattedTransactionType
) {
return !options.startTx || (options.earliestFirst ? return !options.startTx || (options.earliestFirst ?
utils.compareTransactions(tx, options.startTx) > 0 : utils.compareTransactions(tx, options.startTx) > 0 :
utils.compareTransactions(tx, options.startTx) < 0) utils.compareTransactions(tx, options.startTx) < 0)

View File

@@ -1,44 +0,0 @@
import {Amount} from '../common/types/objects'
export type OrderSpecification = {
direction: string,
quantity: Amount,
totalPrice: Amount,
immediateOrCancel?: boolean,
fillOrKill?: boolean,
// If enabled, the offer will not consume offers that exactly match it, and
// instead becomes an Offer node in the ledger. It will still consume offers
// that cross it.
passive?: boolean
}
export type Order = {
specification: OrderSpecification,
properties: {
maker: string,
sequence: number,
makerExchangeRate: string
}
}
export type GetLedger = {
// TODO: properties in type don't match response object. Fix!
// accepted: boolean,
// closed: boolean,
stateHash: string,
closeTime: string,
closeTimeResolution: number,
closeFlags: number,
ledgerHash: string,
ledgerVersion: number,
parentLedgerHash: string,
parentCloseTime: string,
totalDrops: string,
transactionHash: string,
transactions?: Array<Object>,
rawTransactions?: string,
transactionHashes?: Array<string>,
rawState?: string,
stateHashes?: Array<string>
}

View File

@@ -2,7 +2,7 @@ import * as _ from 'lodash'
import * as assert from 'assert' import * as assert from 'assert'
import * as common from '../common' import * as common from '../common'
import {Connection} from '../common' import {Connection} from '../common'
import {TransactionType} from './transaction-types' import {FormattedTransactionType} from '../transaction/types'
import {Issue} from '../common/types/objects' import {Issue} from '../common/types/objects'
export type RecursiveData = { export type RecursiveData = {
@@ -78,7 +78,8 @@ function signum(num) {
* them based on TransactionIndex * them based on TransactionIndex
* See: https://ripple.com/build/transactions/ * See: https://ripple.com/build/transactions/
*/ */
function compareTransactions(first: TransactionType, second: TransactionType function compareTransactions(
first: FormattedTransactionType, second: FormattedTransactionType
): number { ): number {
if (!first.outcome || !second.outcome) { if (!first.outcome || !second.outcome) {
return 0 return 0

View File

@@ -2,16 +2,18 @@ import * as _ from 'lodash'
import * as utils from './utils' import * as utils from './utils'
const offerFlags = utils.common.txFlags.OfferCreate const offerFlags = utils.common.txFlags.OfferCreate
import {validate, iso8601ToRippleTime} from '../common' import {validate, iso8601ToRippleTime} from '../common'
import {Instructions, Prepare} from './types' import {Instructions, Prepare, OfferCreateTransaction} from './types'
import {Order} from '../ledger/transaction-types' import {FormattedOrderSpecification} from '../common/types/objects/index'
function createOrderTransaction(account: string, order: Order): Object { function createOrderTransaction(
account: string, order: FormattedOrderSpecification
): OfferCreateTransaction {
const takerPays = utils.common.toRippledAmount(order.direction === 'buy' ? const takerPays = utils.common.toRippledAmount(order.direction === 'buy' ?
order.quantity : order.totalPrice) order.quantity : order.totalPrice)
const takerGets = utils.common.toRippledAmount(order.direction === 'buy' ? const takerGets = utils.common.toRippledAmount(order.direction === 'buy' ?
order.totalPrice : order.quantity) order.totalPrice : order.quantity)
const txJSON: any = { const txJSON: Partial<OfferCreateTransaction> = {
TransactionType: 'OfferCreate', TransactionType: 'OfferCreate',
Account: account, Account: account,
TakerGets: takerGets, TakerGets: takerGets,
@@ -39,10 +41,10 @@ function createOrderTransaction(account: string, order: Order): Object {
if (order.memos !== undefined) { if (order.memos !== undefined) {
txJSON.Memos = _.map(order.memos, utils.convertMemo) txJSON.Memos = _.map(order.memos, utils.convertMemo)
} }
return txJSON return txJSON as OfferCreateTransaction
} }
function prepareOrder(address: string, order: Order, function prepareOrder(address: string, order: FormattedOrderSpecification,
instructions: Instructions = {} instructions: Instructions = {}
): Promise<Prepare> { ): Promise<Prepare> {
validate.prepareOrder({address, order, instructions}) validate.prepareOrder({address, order, instructions})

View File

@@ -6,36 +6,12 @@ const validate = utils.common.validate
const AccountFlagIndices = utils.common.constants.AccountFlagIndices const AccountFlagIndices = utils.common.constants.AccountFlagIndices
const AccountFields = utils.common.constants.AccountFields const AccountFields = utils.common.constants.AccountFields
import {Instructions, Prepare} from './types' import {Instructions, Prepare} from './types'
import {Memo} from '../common/types/objects' import {FormattedSettings, WeightedSigner} from '../common/types/objects'
export type WeightedSigner = {address: string, weight: number}
export type SettingsSigners = {
threshold?: number,
weights: WeightedSigner[]
}
export type Settings = {
passwordSpent?: boolean,
requireDestinationTag?: boolean,
requireAuthorization?: boolean,
disallowIncomingXRP?: boolean,
disableMasterKey?: boolean,
enableTransactionIDTracking?: boolean,
noFreeze?: boolean,
globalFreeze?: boolean,
defaultRipple?: boolean,
emailHash?: string,
messageKey?: string,
domain?: string,
transferRate?: number,
regularKey?: string,
signers?: SettingsSigners,
memos?: Memo[]
}
// Emptry string passed to setting will clear it // Emptry string passed to setting will clear it
const CLEAR_SETTING = null const CLEAR_SETTING = null
function setTransactionFlags(txJSON: any, values: Settings) { function setTransactionFlags(txJSON: any, values: FormattedSettings) {
const keys = Object.keys(values) const keys = Object.keys(values)
assert(keys.length === 1, 'ERROR: can only set one setting per transaction') assert(keys.length === 1, 'ERROR: can only set one setting per transaction')
const flagName = keys[0] const flagName = keys[0]
@@ -50,7 +26,7 @@ function setTransactionFlags(txJSON: any, values: Settings) {
} }
} }
function setTransactionFields(txJSON: Object, input: Settings) { function setTransactionFields(txJSON: Object, input: FormattedSettings) {
const fieldSchema = AccountFields const fieldSchema = AccountFields
for (const fieldName in fieldSchema) { for (const fieldName in fieldSchema) {
const field = fieldSchema[fieldName] const field = fieldSchema[fieldName]
@@ -101,7 +77,7 @@ function formatSignerEntry(signer: WeightedSigner): Object {
} }
function createSettingsTransactionWithoutMemos( function createSettingsTransactionWithoutMemos(
account: string, settings: Settings account: string, settings: FormattedSettings
): any { ): any {
if (settings.regularKey !== undefined) { if (settings.regularKey !== undefined) {
const removeRegularKey = { const removeRegularKey = {
@@ -137,7 +113,7 @@ function createSettingsTransactionWithoutMemos(
return txJSON return txJSON
} }
function createSettingsTransaction(account: string, settings: Settings function createSettingsTransaction(account: string, settings: FormattedSettings
): Object { ): Object {
const txJSON = createSettingsTransactionWithoutMemos(account, settings) const txJSON = createSettingsTransactionWithoutMemos(account, settings)
if (settings.memos !== undefined) { if (settings.memos !== undefined) {
@@ -146,7 +122,7 @@ function createSettingsTransaction(account: string, settings: Settings
return txJSON return txJSON
} }
function prepareSettings(address: string, settings: Settings, function prepareSettings(address: string, settings: FormattedSettings,
instructions: Instructions = {} instructions: Instructions = {}
): Promise<Prepare> { ): Promise<Prepare> {
validate.prepareSettings({address, settings, instructions}) validate.prepareSettings({address, settings, instructions})

View File

@@ -1,4 +1,14 @@
import {
FormattedOrderSpecification,
FormattedTrustline,
Adjustment,
RippledAmount,
Memo,
FormattedSettings
} from '../common/types/objects'
import {ApiMemo} from './utils'
export type Instructions = { export type Instructions = {
sequence?: number, sequence?: number,
fee?: string, fee?: string,
@@ -25,3 +35,100 @@ export type Submit = {
txBlob?: string, txBlob?: string,
txJson?: Object txJson?: Object
} }
export interface OfferCreateTransaction {
TransactionType: 'OfferCreate',
Account: string,
Fee: string,
Flags: number,
LastLedgerSequence: number,
Sequence: number,
TakerGets: RippledAmount,
TakerPays: RippledAmount,
Expiration?: number,
OfferSequence?: number,
Memos: {Memo: ApiMemo}[]
}
export type Outcome = {
result: string,
ledgerVersion: number,
indexInLedger: number,
fee: string,
balanceChanges: {
[key: string]: [{
currency: string,
counterparty?: string,
value: string
}]
},
orderbookChanges: Object,
timestamp?: string
}
export type FormattedOrderCancellation = {
orderSequence: number
}
export type FormattedPayment = {
source: Adjustment,
destination: Adjustment,
paths?: string,
memos?: Array<Memo>,
invoiceID?: string,
allowPartialPayment?: boolean,
noDirectRipple?: boolean,
limitQuality?: boolean
}
export type FormattedPaymentTransaction = {
type: string,
specification: FormattedPayment,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type FormattedOrderTransaction = {
type: string,
specification: FormattedOrderSpecification,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type FormattedOrderCancellationTransaction = {
type: string,
specification: FormattedOrderCancellation,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type FormattedTrustlineTransaction = {
type: string,
specification: FormattedTrustline,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type FormattedSettingsTransaction = {
type: string,
specification: FormattedSettings,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type FormattedTransactionType =
FormattedPaymentTransaction |
FormattedOrderTransaction |
FormattedOrderCancellationTransaction |
FormattedTrustlineTransaction |
FormattedSettingsTransaction

View File

@@ -1124,9 +1124,11 @@ describe('RippleAPI', function () {
}); });
it('getSettings - invalid options', function () { it('getSettings - invalid options', function () {
assert.throws(() => { return this.api.getSettings(address, { invalid: 'options' }).then(() => {
this.api.getSettings(address, { invalid: 'options' }); assert(false, 'Should throw ValidationError');
}, this.api.errors.ValidationError); }).catch(error => {
assert(error instanceof this.api.errors.ValidationError);
});
}); });
it('getAccountInfo', function () { it('getAccountInfo', function () {