feat(tickets): first commit, preparePayment and prepareTicket

This commit is contained in:
Javi
2020-11-05 12:41:54 +01:00
parent 382cf4cb1f
commit c7e08378ac
20 changed files with 249 additions and 10 deletions

View File

@@ -64,7 +64,7 @@
"doctoc": "doctoc docs/index.md --title '# RippleAPI Reference' --github --maxlevel 2",
"docgen": "node --harmony scripts/build_docs.js",
"prepublish": "yarn clean && yarn build",
"test": "TS_NODE_PROJECT=src/tsconfig.json nyc mocha --exit",
"test": "TS_NODE_PROJECT=src/tsconfig.json nyc mocha --exit -g 'creates a ticket successfully with another ticket'",
"test:watch": "TS_NODE_PROJECT=src/tsconfig.json mocha --watch --reporter dot",
"format": "prettier --write '{src,test}/**/*.ts'",
"lint": "eslint 'src/**/*.ts' 'test/*-test.{ts,js}'",

View File

@@ -43,6 +43,7 @@ import prepareCheckCreate from './transaction/check-create'
import prepareCheckCancel from './transaction/check-cancel'
import prepareCheckCash from './transaction/check-cash'
import prepareSettings from './transaction/settings'
import prepareTicket from './transaction/ticket'
import sign from './transaction/sign'
import combine from './transaction/combine'
import submit from './transaction/submit'
@@ -395,6 +396,7 @@ class RippleAPI extends EventEmitter {
prepareCheckCreate = prepareCheckCreate
prepareCheckCash = prepareCheckCash
prepareCheckCancel = prepareCheckCancel
prepareTicket = prepareTicket
prepareSettings = prepareSettings
sign = sign
combine = combine

View File

@@ -13,6 +13,7 @@ function loadSchemas() {
require('./schemas/objects/hash128.json'),
require('./schemas/objects/hash256.json'),
require('./schemas/objects/sequence.json'),
require('./schemas/objects/ticket-sequence.json'),
require('./schemas/objects/signature.json'),
require('./schemas/objects/issue.json'),
require('./schemas/objects/ledger-version.json'),
@@ -115,6 +116,7 @@ function loadSchemas() {
require('./schemas/input/prepare-check-create.json'),
require('./schemas/input/prepare-check-cash.json'),
require('./schemas/input/prepare-check-cancel.json'),
require('./schemas/input/prepare-ticket.json'),
require('./schemas/input/compute-ledger-hash.json'),
require('./schemas/input/sign.json'),
require('./schemas/input/submit.json'),

View File

@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "prepareTicketParameters",
"type": "object",
"properties": {
"address": {
"$ref": "address",
"description": "The address of the account that is creating the transaction."
},
"ticketCount": {
"type": "number",
"description": "The number of tickets to be created."
},
"instructions": {"$ref": "instructions"}
},
"additionalProperties": false,
"required": ["address", "ticketCount"]
}

View File

@@ -9,6 +9,10 @@
"description": "The initiating account's sequence number for this transaction.",
"$ref": "sequence"
},
"ticketSequence": {
"description": "The ticket sequence to be used for this transaction.",
"$ref": "ticket-sequence"
},
"fee": {
"description": "An exact fee to pay for the transaction, before multiplying for multi-signed transactions. See [Transaction Fees](#transaction-fees) for more information.",
"$ref": "value"
@@ -45,6 +49,10 @@
{
"description": "maxLedgerVersion and maxLedgerVersionOffset are mutually exclusive",
"required": ["maxLedgerVersion", "maxLedgerVersionOffset"]
},
{
"description": "sequence and ticketSequence are mutually exclusive",
"required": ["sequence", "ticketSequence"]
}
]
}

View File

@@ -4,5 +4,5 @@
"link": "account-sequence-number",
"description": "An account transaction sequence number",
"type": "integer",
"minimum": 1
"minimum": 0
}

View File

@@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "ticket-sequence",
"link": "account-sequence-number",
"description": "An account transaction tickt sequence number",
"type": "integer",
"minimum": 1
}

View File

@@ -20,6 +20,10 @@
"$ref": "sequence",
"description": "The initiating account's sequence number for this transaction."
},
"ticketSequence": {
"$ref": "ticket-sequence",
"description": "The initiating account's ticket sequence number for this transaction."
},
"maxLedgerVersion": {
"oneOf": [
{"$ref": "ledgerVersion"},
@@ -29,8 +33,14 @@
}
},
"additionalProperties": false,
"required": ["fee", "sequence", "maxLedgerVersion"]
}
"required": ["fee", "maxLedgerVersion"],
"anyOf": [
{ "required":
[ "sequence" ] },
{ "required":
[ "ticketSequence" ] }
]
}
},
"additionalProperties": false,
"required": ["txJSON", "instructions"]

View File

@@ -132,6 +132,11 @@ export const prepareCheckCancel = _.partial(
'prepareCheckCancelParameters'
)
export const prepareTicket = _.partial(
schemaValidate,
'prepareTicketParameters'
)
export const sign = _.partial(schemaValidate, 'signParameters')
export const combine = _.partial(schemaValidate, 'combineParameters')

View File

@@ -0,0 +1,11 @@
import * as assert from 'assert'
import {removeUndefined} from '../../common'
function parseTicketCreate(tx: any): object {
assert.ok(tx.TransactionType === 'TicketCreate')
return removeUndefined({
ticketCount: tx.TicketCount
})
}
export default parseTicketCreate

View File

@@ -16,6 +16,7 @@ import parsePayment from './payment'
import parsePaymentChannelClaim from './payment-channel-claim'
import parsePaymentChannelCreate from './payment-channel-create'
import parsePaymentChannelFund from './payment-channel-fund'
import parseTicketCreate from './ticket-create'
import parseTrustline from './trustline'
import parseAmendment from './amendment' // pseudo-transaction
@@ -41,6 +42,7 @@ function parseTransactionType(type) {
PaymentChannelFund: 'paymentChannelFund',
SetRegularKey: 'settings',
SignerListSet: 'settings',
TicketCreate: 'ticketCreate',
TrustSet: 'trustline',
EnableAmendment: 'amendment', // pseudo-transaction
@@ -68,6 +70,7 @@ function parseTransaction(tx: any, includeRawTransaction: boolean): any {
paymentChannelClaim: parsePaymentChannelClaim,
paymentChannelCreate: parsePaymentChannelCreate,
paymentChannelFund: parsePaymentChannelFund,
ticketCreate: parseTicketCreate,
trustline: parseTrustline,
amendment: parseAmendment, // pseudo-transaction

View File

@@ -58,7 +58,6 @@ function signWithKeypair(
}
const serialized = binaryCodec.encode(txToSignAndEncode)
checkTxSerialization(serialized, tx)
return {

45
src/transaction/ticket.ts Normal file
View File

@@ -0,0 +1,45 @@
import * as _ from 'lodash'
import * as utils from './utils'
import {Prepare, TransactionJSON, Instructions} from './types'
import {RippleAPI} from '..'
const validate = utils.common.validate
const ValidationError = utils.common.errors.ValidationError
export interface Ticket {
account: string,
sequence: number
}
function createTicketTransaction(
account: string,
ticketCount: number
): TransactionJSON {
if (!ticketCount || ticketCount === 0) throw new ValidationError('Ticket count must be greater than 0.')
const txJSON: any = {
TransactionType: 'TicketCreate',
Account: account,
TicketCount: ticketCount
}
return txJSON
}
function prepareTicket(
this: RippleAPI,
address: string,
ticketCount: number,
instructions: Instructions = {}
): Promise<Prepare> {
try {
validate.prepareTicket({address, ticketCount, instructions})
const txJSON = createTicketTransaction(address, ticketCount)
return utils.prepareTransaction(txJSON, this, instructions)
} catch (e) {
return Promise.reject(e)
}
}
export default prepareTicket

View File

@@ -19,6 +19,7 @@ export type TransactionJSON = {
export type Instructions = {
sequence?: number
ticketSequence?: number
fee?: string
// @deprecated
maxFee?: string
@@ -31,7 +32,8 @@ export type Prepare = {
txJSON: string
instructions: {
fee: string
sequence: number
sequence?: number
ticketSequence?: number
maxLedgerVersion?: number
}
}

View File

@@ -23,10 +23,14 @@ export type ApiMemo = {
function formatPrepareResponse(txJSON: any): Prepare {
const instructions = {
fee: common.dropsToXrp(txJSON.Fee),
sequence: txJSON.Sequence,
maxLedgerVersion:
txJSON.LastLedgerSequence === undefined ? null : txJSON.LastLedgerSequence
}
if (txJSON.TicketSequence !== undefined) {
instructions['ticketSequence'] = txJSON.TicketSequence
} else {
instructions['sequence'] = txJSON.Sequence
}
return {
txJSON: JSON.stringify(txJSON),
instructions
@@ -111,13 +115,15 @@ function prepareTransaction(
api: RippleAPI,
instructions: Instructions
): Promise<Prepare> {
common.validate.instructions(instructions)
common.validate.tx_json(txJSON)
const disallowedFieldsInTxJSON = [
'maxLedgerVersion',
'maxLedgerVersionOffset',
'fee',
'sequence'
'sequence',
'ticketSequence'
]
const badFields = disallowedFieldsInTxJSON.filter(field => txJSON[field])
if (badFields.length) {
@@ -307,6 +313,7 @@ function prepareTransaction(
}
async function prepareSequence(): Promise<void> {
if (instructions.sequence !== undefined) {
if (
newTxJSON.Sequence === undefined ||
@@ -323,10 +330,18 @@ function prepareTransaction(
)
}
}
if (newTxJSON.Sequence !== undefined) {
return Promise.resolve()
}
// Ticket Sequence
if (instructions.ticketSequence !== undefined) {
newTxJSON.Sequence = 0
newTxJSON.TicketSequence = instructions.ticketSequence
return Promise.resolve()
}
try {
// Consider requesting from the 'current' ledger (instead of 'validated').
const response = await api.request('account_info', {

View File

@@ -489,5 +489,44 @@ export default <TestSuite>{
instructionsWithMaxLedgerVersionOffset
)
assertResultMatch(response, expectedResponse, 'prepare')
}
},
// Tickets
'preparePayment with ticketSequence': async (api, address) => {
const version = await api.getLedgerVersion()
const localInstructions = {
maxLedgerVersion: version + 100,
fee: '0.000012',
ticketSequence: 23
}
const response = await api.preparePayment(
address,
REQUEST_FIXTURES.allOptions,
localInstructions
)
assertResultMatch(response, RESPONSE_FIXTURES.ticketSequence, 'prepare')
},
'throws when both sequence and ticketSequence are set': async (
api,
address
) => {
const version = await api.getLedgerVersion()
const localInstructions = {
maxLedgerVersion: version + 100,
fee: '0.000012',
ticketSequence: 23,
sequence: 12
}
return assertRejects(
api.preparePayment(
address,
REQUEST_FIXTURES.allOptions,
localInstructions
),
ValidationError,
'instance.instructions is of prohibited type [object Object]'
)
},
}

View File

@@ -0,0 +1,53 @@
import {assertResultMatch, TestSuite} from '../../utils'
// import responses from '../../fixtures/responses'
// import requests from '../../fixtures/requests'
// import {ValidationError} from 'ripple-api/common/errors'
// import binary from 'ripple-binary-codec'
// import assert from 'assert-diff'
// import {RippleAPI} from 'ripple-api'
// const {schemaValidator} = RippleAPI._PRIVATE
// const instructionsWithMaxLedgerVersionOffset = {maxLedgerVersionOffset: 100}
// const {preparePayment: REQUEST_FIXTURES} = requests
// const {preparePayment: RESPONSE_FIXTURES} = responses
// const ADDRESS = 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
/**
* Every test suite exports their tests in the default object.
* - Check out the "TestSuite" type for documentation on the interface.
* - Check out "test/api/index.ts" for more information about the test runner.
*/
export default <TestSuite>{
'creates a ticket successfully with a sequence number': async (api, address) => {
const expected = {
txJSON:
'{"TransactionType":"TicketCreate", "TicketCount": 2, "Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Flags":2147483648,"LastLedgerSequence":8819954,"Sequence":23,"Fee":"12"}',
instructions: {
maxLedgerVersion: 8819954,
sequence: 23,
fee: '0.000012'
}
}
const response = await api.prepareTicket(address, 2)
assertResultMatch(response, expected, 'prepare')
},
'creates a ticket successfully with another ticket': async (api, address) => {
const expected = {
txJSON:
'{"TransactionType":"TicketCreate", "TicketCount": 1, "Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Flags":2147483648,"LastLedgerSequence":8819954,"Sequence": 0,"TicketSequence":23,"Fee":"12"}',
instructions: {
maxLedgerVersion: 8819954,
ticketSequence: 23,
fee: '0.000012'
}
}
const instructions = {
maxLedgerVersion: 8819954,
ticketSequence: 23,
fee: '0.000012'
}
const response = await api.prepareTicket(address, 1, instructions)
assertResultMatch(response, expected, 'prepare')
}
}

View File

@@ -108,7 +108,8 @@ module.exports = {
minAmountXRPXRP: require('./prepare-payment-min-amount-xrp-xrp.json'),
allOptions: require('./prepare-payment-all-options.json'),
noCounterparty: require('./prepare-payment-no-counterparty.json'),
minAmount: require('./prepare-payment-min-amount.json')
minAmount: require('./prepare-payment-min-amount.json'),
ticketSequence: require('./prepare-payment-ticket-sequence.json')
},
prepareSettings: {
regularKey: require('./prepare-settings-regular-key.json'),

View File

@@ -0,0 +1,8 @@
{
"txJSON": "{\"Flags\":2147811328,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":\"10000\",\"InvoiceID\":\"A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A\",\"SourceTag\":14,\"DestinationTag\":58,\"Memos\":[{\"Memo\":{\"MemoType\":\"74657374\",\"MemoFormat\":\"746578742F706C61696E\",\"MemoData\":\"7465787465642064617461\"}}],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":0,\"TicketSequence\":23}",
"instructions": {
"fee": "0.000012",
"ticketSequence": 23,
"maxLedgerVersion": 8820051
}
}

View File

@@ -348,6 +348,16 @@ describe('integration tests', function() {
})
})
it('ticket', function() {
return this.api.getLedgerVersion().then(ledgerVersion => {
return this.api
.prepareTicket(address, 1, instructions)
.then(prepared =>
testTransaction(this, 'ticket', ledgerVersion, prepared)
)
})
})
it('isConnected', function() {
assert(this.api.isConnected())
})