Compare commits

...

10 Commits

Author SHA1 Message Date
Elliot Lee
90be539b09 Release 0.19.1 2018-03-22 14:12:22 -07:00
Fred K. Schott
43c08e5ea7 Clean up min/max adjustments (#873) 2018-03-22 12:31:44 -07:00
Fred K. Schott
3f22b12216 Add linting to travis (#872) 2018-03-20 14:30:37 -07:00
darkmemo
a72041a321 Fix Payment source & destination types (#870)
Payment `source` and `destination` are defined as intersection types, `Adjustment & MaxAdjustment` and  `Adjustment & MinAdjustment` respectively. But they should be union types instead. This problem was introduced during js to ts conversion.

Fixes #866
2018-03-20 11:26:41 -07:00
Elliot Lee
71a0c16fec Ledger object - accountState supersedes accounts (#868)
This appears to originate from a mistake in the docs.
The actual name of the field is `accountState`.

See:
9af994ceb4/src/ripple/app/ledger/impl/LedgerToJson.cpp (L167)

* Remove TODOs addressed by docs PR

https://github.com/ripple/ripple-dev-portal/pull/324
2018-03-19 14:52:18 -07:00
Elliot Lee
27ab98160a Fix link to checkCash (#871)
Fix source file for 4eaaa8188d
2018-03-19 14:41:18 -07:00
Ryan Young
4eaaa8188d Fix link to checkCash (#871)
#checkCash -- > #check-cash
2018-03-19 14:39:54 -07:00
Fred K. Schott
187154a2b0 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
2018-03-14 16:08:57 -07:00
Elliot Lee
c175e3f58e Point to types in package.json (#863) 2018-03-06 23:15:55 -08:00
Fred K. Schott
2ea22a099e Don't generate TS definitions for browser build (#864) 2018-03-06 23:15:11 -08:00
41 changed files with 578 additions and 467 deletions

View File

@@ -7,3 +7,4 @@ script:
- yarn compile
- yarn test
- yarn build
- yarn lint

View File

@@ -50,10 +50,12 @@ function getWebpackConfig(extension, overrides) {
use: 'null',
}, {
test: /\.ts$/,
use: 'ts-loader',
include: [
path.resolve(__dirname, 'src')
]
use: [{
loader: 'ts-loader',
options: {
compilerOptions: {declaration: false}
},
}],
}, {
test: /\.json/,
use: 'json-loader',

View File

@@ -1,5 +1,21 @@
# ripple-lib Release History
## 0.19.1 (2018-03-22)
+ [Fix: Include TypeScript declarations in npm package](https://github.com/ripple/ripple-lib/pull/863)
+ [Fix: Documentation link to checkCash](https://github.com/ripple/ripple-lib/pull/871)
+ [Internal: Clean up types and migrate more APIs to new request method](https://github.com/ripple/ripple-lib/pull/857)
+ [Internal: Fix Payment source and destination types](https://github.com/ripple/ripple-lib/pull/870)
The SHA-256 checksums for the browser version of this release can be found
below.
```
% shasum -a 256 *
3ed5332aa035c07bae6c1abfdfc8ca77cdbb05cc4b88878f544f1ea4cb793f4d ripple-0.19.1-debug.js
2f5507aa00a40ab6a94de1822af87db5e927edef3885aef5d9b39ccb623ccb54 ripple-0.19.1-min.js
1e439aee1b220242d56ea687a9b55a67b8614212c1ddbd70a4fcf34503fc487a ripple-0.19.1.js
```
## 0.19.0 (2018-03-02)
+ [Add support for Checks](https://github.com/ripple/ripple-lib/pull/853)

View File

@@ -274,7 +274,7 @@ Type | Description
[escrowExecution](#escrow-execution) | An `escrowExecution` transaction unlocks the funds in an escrow and sends them to the destination of the escrow, but it will only work if the cryptographic condition is provided.
[checkCreate](#check-create) | A `checkCreate` transaction creates a check on the ledger, which is a deferred payment that can be cashed by its intended destination.
[checkCancel](#check-cancel) | A `checkCancel` transaction cancels an unreedemed Check, removing it from the ledger without sending any money.
[checkCash](#checkCash) | A `checkCash` transaction redeems a Check to receive up to the amount authorized by the corresponding `checkCreate` transaction. Only the `destination` address of a Check can cash it.
[checkCash](#check-cash) | A `checkCash` transaction redeems a Check to receive up to the amount authorized by the corresponding `checkCreate` transaction. Only the `destination` address of a Check can cash it.
## Transaction Flow

View File

@@ -16,7 +16,7 @@ Type | Description
[escrowExecution](#escrow-execution) | An `escrowExecution` transaction unlocks the funds in an escrow and sends them to the destination of the escrow, but it will only work if the cryptographic condition is provided.
[checkCreate](#check-create) | A `checkCreate` transaction creates a check on the ledger, which is a deferred payment that can be cashed by its intended destination.
[checkCancel](#check-cancel) | A `checkCancel` transaction cancels an unreedemed Check, removing it from the ledger without sending any money.
[checkCash](#checkCash) | A `checkCash` transaction redeems a Check to receive up to the amount authorized by the corresponding `checkCreate` transaction. Only the `destination` address of a Check can cash it.
[checkCash](#check-cash) | A `checkCash` transaction redeems a Check to receive up to the amount authorized by the corresponding `checkCreate` transaction. Only the `destination` address of a Check can cash it.
## Transaction Flow

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.19.0",
"version": "0.19.1",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
@@ -8,6 +8,7 @@
"build/*"
],
"main": "dist/npm/",
"types": "dist/npm/index.d.ts",
"browser": {
"ws": "./dist/npm/common/wswrapper.js"
},

View File

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

View File

@@ -1,4 +1,8 @@
import {AccountRoot, SignerList} from '../objects'
import {
AccountRootLedgerEntry,
SignerListLedgerEntry,
QueueData
} from '../objects'
export interface AccountInfoRequest {
account: string,
@@ -10,27 +14,10 @@ export interface AccountInfoRequest {
}
export interface AccountInfoResponse {
account_data: AccountRoot,
signer_lists?: SignerList[],
account_data: AccountRootLedgerEntry,
signer_lists?: SignerListLedgerEntry[],
ledger_current_index?: number,
ledger_index?: number,
queue_data?: QueueData,
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 {
TakerRequestAmount,
OfferCreateTransaction,
RippledAmount
RippledAmount,
OfferCreateTransaction
} from '../objects'
export interface BookOffersRequest {
@@ -15,15 +15,15 @@ export interface BookOffersRequest {
}
export interface BookOffersResponse {
offers: OrderBookOffer[],
offers: BookOffer[],
ledger_hash?: string,
ledger_current_index?: number,
ledger_index?: number,
marker?: any
}
export interface OrderBookOffer extends OfferCreateTransaction {
quality?: number
export interface BookOffer extends OfferCreateTransaction {
quality?: string
owner_funds?: string,
taker_gets_funded?: RippledAmount,
taker_pays_funded?: RippledAmount

View File

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

View File

@@ -3,3 +3,5 @@ export * from './account_lines'
export * from './account_offers'
export * from './book_offers'
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 './amounts'
export * from './ledger'
export * from './ledger_entries'
export * from './memos'
export * from './orders'
export * from './queue_data'
export * from './settings'
export * from './signers'
export * from './transactions'
export * from './trustlines'

View File

@@ -0,0 +1,22 @@
export interface Ledger {
account_hash: string,
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,
close_flags?: number,
parent_close_time?: number,
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 {
Account: string,
SignerWeight: number

View File

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

View File

@@ -1,51 +1,28 @@
import * as _ from 'lodash'
import * as utils from './utils'
import parseOrderbookOrder from './parse/orderbook-order'
import {
parseOrderbookOrder,
FormattedOrderbookOrder
} from './parse/orderbook-order'
import {validate} from '../common'
import {OrderSpecification} from './types'
import {Amount, Issue} from '../common/types/objects'
import {BookOffer} from '../common/types/commands'
import {RippleAPI} from '../api'
import {OfferCreateTransaction} from '../common/types/objects'
export type OrdersOptions = {
limit?: number,
ledgerVersion?: number
}
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
export type FormattedOrderbook = {
bids: FormattedOrderbookOrder[],
asks: FormattedOrderbookOrder[]
}
function isSameIssue(a: Amount, b: Amount) {
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
}
function flipOrder(order: OrderbookItem) {
function flipOrder(order: FormattedOrderbookOrder) {
const specification = order.specification
const flippedSpecification = {
quantity: specification.totalPrice,
@@ -56,13 +33,13 @@ function flipOrder(order: OrderbookItem) {
return _.merge({}, order, {specification: newSpecification})
}
function alignOrder(base: Amount, order: OrderbookItem) {
function alignOrder(base: Amount, order: FormattedOrderbookOrder) {
const quantity = order.specification.quantity
return isSameIssue(quantity, base) ? order : flipOrder(order)
}
function formatBidsAndAsks(
orderbook: Orderbook, offers: OfferCreateTransaction[]) {
orderbook: OrderbookInfo, offers: BookOffer[]) {
// the "base" currency is the currency that you are buying or selling
// the "counter" is the currency that the "base" is priced in
// 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
// are returned
async function makeRequest(
api: RippleAPI, taker: string, options: OrdersOptions,
api: RippleAPI, taker: string, options: GetOrderbookOptions,
takerGets: Issue, takerPays: Issue
) {
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(
this: RippleAPI,
address: string,
orderbook: Orderbook,
options: OrdersOptions = {}
): Promise<GetOrderbook> {
orderbook: OrderbookInfo,
options: GetOrderbookOptions = {}
): Promise<FormattedOrderbook> {
// 1. Validate
validate.getOrderbook({address, orderbook, options})
// 2. Make Request

View File

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

View File

@@ -3,7 +3,16 @@ import parseAmount from './amount'
import {parseTimestamp, adjustQualityForXRP} from './utils'
import {removeUndefined} from '../../common'
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
function computeQuality(takerGets, takerPays) {
@@ -13,7 +22,9 @@ function computeQuality(takerGets, takerPays) {
// rippled 'account_offers' returns a different format for orders than 'tx'
// 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 takerGetsAmount = parseAmount(order.taker_gets)
const takerPaysAmount = parseAmount(order.taker_pays)
@@ -43,5 +54,3 @@ function parseAccountOrder(address: string, order: any): Order {
return {specification, properties}
}
export default parseAccountOrder

View File

@@ -1,7 +1,28 @@
import * as _ from 'lodash'
import {removeUndefined, rippleTimeToISO8601} from '../../common'
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) {
const transaction = _.assign({}, _.omit(tx, 'metaData'), {
@@ -39,7 +60,7 @@ function parseState(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)
return removeUndefined(Object.assign({
stateHash: ledger.account_hash,
@@ -57,5 +78,3 @@ function parseLedger(ledger: any): GetLedger {
parseState(ledger.accountState)
))
}
export default parseLedger

View File

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

View File

@@ -4,8 +4,25 @@ import {removeUndefined} from '../../common'
import {orderFlags} from './flags'
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 takerGetsAmount = parseAmount(order.TakerGets)
const takerPaysAmount = parseAmount(order.TakerPays)
@@ -14,7 +31,7 @@ function parseOrderbookOrder(order: any): Object {
// note: immediateOrCancel and fillOrKill orders cannot enter the order book
// so we can omit those flags here
const specification = removeUndefined({
const specification: FormattedOrderSpecification = removeUndefined({
direction: direction,
quantity: quantity,
totalPrice: totalPrice,
@@ -40,5 +57,3 @@ function parseOrderbookOrder(order: any): Object {
const state = _.isEmpty(available) ? undefined : available
return removeUndefined({specification, properties, state})
}
export default parseOrderbookOrder

View File

@@ -1,35 +1,8 @@
import {parseTimestamp} from './utils'
import {removeUndefined, dropsToXrp} from '../../common'
import {PayChannelLedgerEntry} from '../../common/types/objects'
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 = {
export type FormattedPaymentChannel = {
account: string,
balance: string,
publicKey: string,
@@ -43,7 +16,9 @@ export type PaymentChannelResponse = {
previousAffectingTransactionLedgerVersion: number
}
function parsePaymentChannel(data: PaymentChannel): PaymentChannelResponse {
export function parsePaymentChannel(
data: PayChannelLedgerEntry
): FormattedPaymentChannel {
return removeUndefined({
account: data.Account,
amount: dropsToXrp(data.Amount),
@@ -59,5 +34,3 @@ function parsePaymentChannel(data: PaymentChannel): PaymentChannelResponse {
previousAffectingTransactionLedgerVersion: data.PreviousTxnLgrSeq
})
}
export default parsePaymentChannel

View File

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

View File

@@ -1,31 +1,15 @@
import * as _ from 'lodash'
import parseFields from './parse/fields'
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
export type SettingsOptions = {
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) {
const settings = {}
for (const flagName in AccountFlags) {
@@ -36,25 +20,26 @@ function parseFlags(value) {
return settings
}
function formatSettings(response) {
function formatSettings(response: AccountInfoResponse) {
const data = response.account_data
const parsedFlags = parseFlags(data.Flags)
const parsedFields = parseFields(data)
return _.assign({}, parsedFlags, parsedFields)
}
function getSettings(address: string, options: SettingsOptions = {}
): Promise<GetSettings> {
async function getSettings(
this: RippleAPI, address: string, options: SettingsOptions = {}
): Promise<FormattedSettings> {
// 1. Validate
validate.getSettings({address, options})
const request = {
command: 'account_info',
// 2. Make Request
const response = await this._request('account_info', {
account: address,
ledger_index: options.ledgerVersion || 'validated',
signer_lists: true
}
return this.connection.request(request).then(formatSettings)
})
// 3. Return Formatted Response
return formatSettings(response)
}
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 {validate, errors} from '../common'
import {Connection} from '../common'
import {
TransactionType, TransactionResponse, TransactionOptions
} from './transaction-types'
import {FormattedTransactionType} 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
): Promise<TransactionType> {
): Promise<FormattedTransactionType> {
if (tx.date) {
return Promise.resolve(tx)
}
@@ -71,7 +81,7 @@ function convertError(connection: Connection, options: TransactionOptions,
}
function formatResponse(options: TransactionOptions, tx: TransactionResponse
): TransactionType {
): FormattedTransactionType {
if (tx.validated !== true || !isTransactionInRange(tx, options)) {
throw new errors.NotFoundError('Transaction not found')
}
@@ -79,7 +89,7 @@ function formatResponse(options: TransactionOptions, tx: TransactionResponse
}
function getTransaction(id: string, options: TransactionOptions = {}
): Promise<TransactionType> {
): Promise<FormattedTransactionType> {
validate.getTransaction({id, options})
const request = {

View File

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

View File

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

View File

@@ -9,9 +9,9 @@ import {Amount, Adjustment, MaxAdjustment,
MinAdjustment, Memo} from '../common/types/objects'
export type Payment = {
source: Adjustment & MaxAdjustment,
destination: Adjustment & MinAdjustment,
export interface Payment {
source: Adjustment | MaxAdjustment,
destination: Adjustment | MinAdjustment,
paths?: string,
memos?: Array<Memo>,
// A 256-bit hash that can be used to identify a particular payment
@@ -30,11 +30,22 @@ import {Amount, Adjustment, MaxAdjustment,
limitQuality?: boolean
}
function isMaxAdjustment(
source: Adjustment | MaxAdjustment): source is MaxAdjustment {
return (source as MaxAdjustment).maxAmount !== undefined
}
function isMinAdjustment(
destination: Adjustment | MinAdjustment): destination is MinAdjustment {
return (destination as MinAdjustment).minAmount !== undefined
}
function isXRPToXRPPayment(payment: Payment): boolean {
const sourceCurrency = _.get(payment, 'source.maxAmount.currency',
_.get(payment, 'source.amount.currency'))
const destinationCurrency = _.get(payment, 'destination.amount.currency',
_.get(payment, 'destination.minAmount.currency'))
const {source, destination} = payment
const sourceCurrency = isMaxAdjustment(source)
? source.maxAmount.currency : source.amount.currency
const destinationCurrency = isMinAdjustment(destination)
? destination.minAmount.currency : destination.amount.currency
return sourceCurrency === 'XRP' && destinationCurrency === 'XRP'
}
@@ -74,21 +85,29 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
throw new ValidationError('address must match payment.source.address')
}
if ((payment.source.maxAmount && payment.destination.minAmount) ||
(payment.source.amount && payment.destination.amount)) {
if (
(isMaxAdjustment(payment.source) && isMinAdjustment(payment.destination))
||
(!isMaxAdjustment(payment.source) && !isMinAdjustment(payment.destination))
) {
throw new ValidationError('payment must specify either (source.maxAmount '
+ 'and destination.amount) or (source.amount and destination.minAmount)')
}
const destinationAmount = isMinAdjustment(payment.destination)
? payment.destination.minAmount : payment.destination.amount
const sourceAmount = isMaxAdjustment(payment.source)
? payment.source.maxAmount : payment.source.amount
// when using destination.minAmount, rippled still requires that we set
// a destination amount in addition to DeliverMin. the destination amount
// is interpreted as the maximum amount to send. we want to be sure to
// send the whole source amount, so we set the destination amount to the
// maximum possible amount. otherwise it's possible that the destination
// cap could be hit before the source cap.
const amount = payment.destination.minAmount && !isXRPToXRPPayment(payment) ?
createMaximalAmount(payment.destination.minAmount) :
(payment.destination.amount || payment.destination.minAmount)
const amount =
(isMinAdjustment(payment.destination) && !isXRPToXRPPayment(payment))
? createMaximalAmount(destinationAmount) : destinationAmount
const txJSON: any = {
TransactionType: 'Payment',
@@ -121,16 +140,14 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
// temREDUNDANT_SEND_MAX removed in:
// https://github.com/ripple/rippled/commit/
// c522ffa6db2648f1d8a987843e7feabf1a0b7de8/
if (payment.allowPartialPayment === true
|| payment.destination.minAmount !== undefined) {
if (payment.allowPartialPayment || isMinAdjustment(payment.destination)) {
txJSON.Flags |= paymentFlags.PartialPayment
}
txJSON.SendMax = toRippledAmount(
payment.source.maxAmount || payment.source.amount)
txJSON.SendMax = toRippledAmount(sourceAmount)
if (payment.destination.minAmount !== undefined) {
txJSON.DeliverMin = toRippledAmount(payment.destination.minAmount)
if (isMinAdjustment(payment.destination)) {
txJSON.DeliverMin = toRippledAmount(destinationAmount)
}
if (payment.paths !== undefined) {

View File

@@ -6,36 +6,12 @@ const validate = utils.common.validate
const AccountFlagIndices = utils.common.constants.AccountFlagIndices
const AccountFields = utils.common.constants.AccountFields
import {Instructions, Prepare} from './types'
import {Memo} 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[]
}
import {FormattedSettings, WeightedSigner} from '../common/types/objects'
// Emptry string passed to setting will clear it
const CLEAR_SETTING = null
function setTransactionFlags(txJSON: any, values: Settings) {
function setTransactionFlags(txJSON: any, values: FormattedSettings) {
const keys = Object.keys(values)
assert(keys.length === 1, 'ERROR: can only set one setting per transaction')
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
for (const fieldName in fieldSchema) {
const field = fieldSchema[fieldName]
@@ -101,7 +77,7 @@ function formatSignerEntry(signer: WeightedSigner): Object {
}
function createSettingsTransactionWithoutMemos(
account: string, settings: Settings
account: string, settings: FormattedSettings
): any {
if (settings.regularKey !== undefined) {
const removeRegularKey = {
@@ -137,7 +113,7 @@ function createSettingsTransactionWithoutMemos(
return txJSON
}
function createSettingsTransaction(account: string, settings: Settings
function createSettingsTransaction(account: string, settings: FormattedSettings
): Object {
const txJSON = createSettingsTransactionWithoutMemos(account, settings)
if (settings.memos !== undefined) {
@@ -146,7 +122,7 @@ function createSettingsTransaction(account: string, settings: Settings
return txJSON
}
function prepareSettings(address: string, settings: Settings,
function prepareSettings(address: string, settings: FormattedSettings,
instructions: Instructions = {}
): Promise<Prepare> {
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 = {
sequence?: number,
fee?: string,
@@ -25,3 +35,100 @@ export type Submit = {
txBlob?: string,
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 () {
assert.throws(() => {
this.api.getSettings(address, { invalid: 'options' });
}, this.api.errors.ValidationError);
return this.api.getSettings(address, { invalid: 'options' }).then(() => {
assert(false, 'Should throw ValidationError');
}).catch(error => {
assert(error instanceof this.api.errors.ValidationError);
});
});
it('getAccountInfo', function () {