feat: add AMM support (#2071)

* update definitions.json

* add AMMInstanceCreate

* renamed LPTokens to LPToken in definitions.json

* update HISTORY.md

* add amm_info RPC command

* add AMMDeposit

* use null check for missing fields

* add AMMWithdraw

* add AMMVote

* fix lint error

* add max trading fee check to AMMVote

* refactor MAX_TRADING_FEE to be in one place

* add AMMBid

* add AuthAccount interface to AMMBid

* refactor tests

* add AMMID to AMMInfoResponse

* update amm_info docstrings

* fix EPrice type to be Amount

* update EPrice validation error message and add missing tests

* update definitions to fix AMM in LEDGER_ENTRY_TYPES

* add missing test case Asset1In and Asset2In valid

* add missing test case Asset1Out and Asset2Out valid

* add negative FeeVal check

* update HISTORY.md to specify XLS-30

* update wording on AMMDeposit & AMMWithdraw validation errors

* add negative TradingFee check

* fix ammInfo response

* add AMMID as optional param in ammInfo response

* fix EPrice validation checks in AMMDeposit & AMMWithdraw

* add VoteSlots as optional param in AMMInfoResponse

* update VoteEntry interface

* fix deposit and withdraw tests

* fix AMMBid ValidationError

* update definitions.json with different AuthAccounts number

* Change amm_info asset parameters to Currency type

* API name changes with updated definitions.json

* rename amm_info param asset1 -> asset

* fix typo

* change AMM_MAX_TRADING_FEE to 1%

* Use Asset/Asset2 instead of AMMID for Deposit/Withdraw/Bid/Vote

* add Deposit/Withdraw flags

* rename FeeVal -> TradingFee in VoteEntry

* rename MinBidPrice -> BidMin and MaxBidPrice -> BidMax

* update definitions to change Asset & Asset2 nth values to 3 & 4

* update definitions

* add Issue type and tests for Asset/Asset2

* remove AMMID from amm_info and use Issue type

* update amm_info fields

* fix lint errors

* update unit tests

* add AMM codec-fixtures

* update Issue type

* add one asset and withdraw all tests

* refactor amm_info response fields to match AMMDevnet

* update definitions.json with refactored error codes

* update ammInfo.ts response model

* remove invalid fields from ammInfo.ts response model

* update time_interval description

* rename test model names and fix lint errors

* add Owner Reserve Fee for AMMCreate transaction

* add missing asset_frozen field

* replace Issue with IssuedCurrency

* refactor: convert flags to number

* update asset pair to use Currency type

* refactor isIssue to isCurrency

* add AMM ledger entry object, lsfAMM flag, amm fields to LedgerEntryRequest

* update definitions.json

* WIP defintions

* update codec-fixtures

* fix definitions test

* update DiscountedFee definition

* update definitions

* update codec-fixtures

* update definitions

* update unit tests

* update amm_info response

* sort imports/exports

* update jsdoc

* update amm_info jsdoc

* update jsdoc

* convert caution to all caps

* add validation for AuthAccounts

* refactor and export interfaces

* use Currency type

* update definitions

* add AMMDelete

* rename Issue to Currency in error message

* mark asset frozen as optional fields

* refactor isAuthAccounts

* add AMMDelete jsdoc

* rename to validateAuthAccounts

* fix typo

* fix typo in unit test

---------

Co-authored-by: Caleb Kniffen <ckniffen@ripple.com>
This commit is contained in:
Omar Khan
2023-08-17 22:42:32 -04:00
committed by GitHub
parent 93ea98ac4a
commit 5581474627
31 changed files with 2392 additions and 58 deletions

View File

@@ -21,6 +21,7 @@
"UInt192": 21, "UInt192": 21,
"UInt384": 22, "UInt384": 22,
"UInt512": 23, "UInt512": 23,
"Issue": 24,
"Transaction": 10001, "Transaction": 10001,
"LedgerEntry": 10002, "LedgerEntry": 10002,
"Validation": 10003, "Validation": 10003,
@@ -44,6 +45,7 @@
"NegativeUNL": 78, "NegativeUNL": 78,
"NFTokenPage": 80, "NFTokenPage": 80,
"NFTokenOffer": 55, "NFTokenOffer": 55,
"AMM": 121,
"Any": -3, "Any": -3,
"Child": -2, "Child": -2,
"Nickname": 110, "Nickname": 110,
@@ -271,6 +273,26 @@
"type": "UInt16" "type": "UInt16"
} }
], ],
[
"TradingFee",
{
"nth": 5,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "UInt16"
}
],
[
"DiscountedFee",
{
"nth": 6,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "UInt16"
}
],
[ [
"Version", "Version",
{ {
@@ -771,6 +793,26 @@
"type": "UInt32" "type": "UInt32"
} }
], ],
[
"VoteWeight",
{
"nth": 48,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "UInt32"
}
],
[
"FirstNFTokenSequence",
{
"nth": 50,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "UInt32"
}
],
[ [
"IndexNext", "IndexNext",
{ {
@@ -1121,6 +1163,16 @@
"type": "Hash256" "type": "Hash256"
} }
], ],
[
"AMMID",
{
"nth": 14,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Hash256"
}
],
[ [
"BookDirectory", "BookDirectory",
{ {
@@ -1391,6 +1443,36 @@
"type": "Amount" "type": "Amount"
} }
], ],
[
"Amount2",
{
"nth": 11,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[
"BidMin",
{
"nth": 12,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[
"BidMax",
{
"nth": 13,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[ [
"MinimumOffer", "MinimumOffer",
{ {
@@ -1431,6 +1513,86 @@
"type": "Amount" "type": "Amount"
} }
], ],
[
"BaseFeeDrops",
{
"nth": 22,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[
"ReserveBaseDrops",
{
"nth": 23,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[
"ReserveIncrementDrops",
{
"nth": 24,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[
"LPTokenOut",
{
"nth": 25,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[
"LPTokenIn",
{
"nth": 26,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[
"EPrice",
{
"nth": 27,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[
"Price",
{
"nth": 28,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[
"LPTokenBalance",
{
"nth": 31,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[ [
"PublicKey", "PublicKey",
{ {
@@ -1821,6 +1983,26 @@
"type": "PathSet" "type": "PathSet"
} }
], ],
[
"Asset",
{
"nth": 3,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Issue"
}
],
[
"Asset2",
{
"nth": 4,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Issue"
}
],
[ [
"TransactionMetaData", "TransactionMetaData",
{ {
@@ -2031,6 +2213,36 @@
"type": "STObject" "type": "STObject"
} }
], ],
[
"VoteEntry",
{
"nth": 25,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STObject"
}
],
[
"AuctionSlot",
{
"nth": 26,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STObject"
}
],
[
"AuthAccount",
{
"nth": 27,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STObject"
}
],
[ [
"Signers", "Signers",
{ {
@@ -2121,6 +2333,16 @@
"type": "STArray" "type": "STArray"
} }
], ],
[
"VoteSlots",
{
"nth": 12,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STArray"
}
],
[ [
"Majorities", "Majorities",
{ {
@@ -2170,6 +2392,16 @@
"isSigningField": true, "isSigningField": true,
"type": "STArray" "type": "STArray"
} }
],
[
"AuthAccounts",
{
"nth": 25,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STArray"
}
] ]
], ],
"TRANSACTION_RESULTS": { "TRANSACTION_RESULTS": {
@@ -2228,6 +2460,7 @@
"temUNKNOWN": -264, "temUNKNOWN": -264,
"temSEQ_AND_TICKET": -263, "temSEQ_AND_TICKET": -263,
"temBAD_NFTOKEN_TRANSFER_FEE": -262, "temBAD_NFTOKEN_TRANSFER_FEE": -262,
"temBAD_AMM_TOKENS": -261,
"tefFAILURE": -199, "tefFAILURE": -199,
"tefALREADY": -198, "tefALREADY": -198,
@@ -2263,6 +2496,7 @@
"terNO_RIPPLE": -90, "terNO_RIPPLE": -90,
"terQUEUED": -89, "terQUEUED": -89,
"terPRE_TICKET": -88, "terPRE_TICKET": -88,
"terNO_AMM": -87,
"tesSUCCESS": 0, "tesSUCCESS": 0,
@@ -2311,7 +2545,15 @@
"tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158, "tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158,
"tecINSUFFICIENT_FUNDS": 159, "tecINSUFFICIENT_FUNDS": 159,
"tecOBJECT_NOT_FOUND": 160, "tecOBJECT_NOT_FOUND": 160,
"tecINSUFFICIENT_PAYMENT": 161 "tecINSUFFICIENT_PAYMENT": 161,
"tecUNFUNDED_AMM": 162,
"tecAMM_BALANCE": 163,
"tecAMM_FAILED": 164,
"tecAMM_INVALID_TOKENS": 165,
"tecAMM_EMPTY": 166,
"tecAMM_NOT_EMPTY": 167,
"tecAMM_ACCOUNT": 168,
"tecINCOMPLETE": 169
}, },
"TRANSACTION_TYPES": { "TRANSACTION_TYPES": {
"Invalid": -1, "Invalid": -1,
@@ -2344,6 +2586,12 @@
"NFTokenCancelOffer": 28, "NFTokenCancelOffer": 28,
"NFTokenAcceptOffer": 29, "NFTokenAcceptOffer": 29,
"Clawback": 30, "Clawback": 30,
"AMMCreate": 35,
"AMMDeposit": 36,
"AMMWithdraw": 37,
"AMMVote": 38,
"AMMBid": 39,
"AMMDelete": 40,
"EnableAmendment": 100, "EnableAmendment": 100,
"SetFee": 101, "SetFee": 101,
"UNLModify": 102 "UNLModify": 102

View File

@@ -5,6 +5,7 @@ import { Currency } from './currency'
import { Hash128 } from './hash-128' import { Hash128 } from './hash-128'
import { Hash160 } from './hash-160' import { Hash160 } from './hash-160'
import { Hash256 } from './hash-256' import { Hash256 } from './hash-256'
import { Issue } from './issue'
import { PathSet } from './path-set' import { PathSet } from './path-set'
import { STArray } from './st-array' import { STArray } from './st-array'
import { STObject } from './st-object' import { STObject } from './st-object'
@@ -24,6 +25,7 @@ const coreTypes: Record<string, typeof SerializedType> = {
Hash128, Hash128,
Hash160, Hash160,
Hash256, Hash256,
Issue,
PathSet, PathSet,
STArray, STArray,
STObject, STObject,

View File

@@ -0,0 +1,96 @@
import { BinaryParser } from '../serdes/binary-parser'
import { AccountID } from './account-id'
import { Currency } from './currency'
import { JsonObject, SerializedType } from './serialized-type'
import { Buffer } from 'buffer/'
/**
* Interface for JSON objects that represent amounts
*/
interface IssueObject extends JsonObject {
currency: string
issuer?: string
}
/**
* Type guard for AmountObject
*/
function isIssueObject(arg): arg is IssueObject {
const keys = Object.keys(arg).sort()
if (keys.length === 1) {
return keys[0] === 'currency'
}
return keys.length === 2 && keys[0] === 'currency' && keys[1] === 'issuer'
}
/**
* Class for serializing/Deserializing Amounts
*/
class Issue extends SerializedType {
static readonly ZERO_ISSUED_CURRENCY: Issue = new Issue(Buffer.alloc(20))
constructor(bytes: Buffer) {
super(bytes ?? Issue.ZERO_ISSUED_CURRENCY.bytes)
}
/**
* Construct an amount from an IOU or string amount
*
* @param value An Amount, object representing an IOU, or a string
* representing an integer amount
* @returns An Amount object
*/
static from<T extends Issue | IssueObject>(value: T): Issue {
if (value instanceof Issue) {
return value
}
if (isIssueObject(value)) {
const currency = Currency.from(value.currency).toBytes()
if (value.issuer == null) {
return new Issue(currency)
}
const issuer = AccountID.from(value.issuer).toBytes()
return new Issue(Buffer.concat([currency, issuer]))
}
throw new Error('Invalid type to construct an Amount')
}
/**
* Read an amount from a BinaryParser
*
* @param parser BinaryParser to read the Amount from
* @returns An Amount object
*/
static fromParser(parser: BinaryParser): Issue {
const currency = parser.read(20)
if (new Currency(currency).toJSON() === 'XRP') {
return new Issue(currency)
}
const currencyAndIssuer = [currency, parser.read(20)]
return new Issue(Buffer.concat(currencyAndIssuer))
}
/**
* Get the JSON representation of this Amount
*
* @returns the JSON interpretation of this.bytes
*/
toJSON(): IssueObject {
const parser = new BinaryParser(this.toString())
const currency = Currency.fromParser(parser) as Currency
if (currency.toJSON() === 'XRP') {
return { currency: currency.toJSON() }
}
const issuer = AccountID.fromParser(parser) as AccountID
return {
currency: currency.toJSON(),
issuer: issuer.toJSON(),
}
}
}
export { Issue, IssueObject }

View File

@@ -129,7 +129,7 @@ describe('encode and decode using new types as a parameter', function () {
// Normally this would be generated directly from rippled with something like `server_definitions`. // Normally this would be generated directly from rippled with something like `server_definitions`.
// Added here to make it easier to see what is actually changing in the definitions.json file. // Added here to make it easier to see what is actually changing in the definitions.json file.
const definitions = JSON.parse(JSON.stringify(normalDefinitionsJson)) const definitions = JSON.parse(JSON.stringify(normalDefinitionsJson))
definitions.TYPES.NewType = 24 definitions.TYPES.NewType = 31
definitions.FIELDS.push([ definitions.FIELDS.push([
'TestField', 'TestField',
{ {

View File

@@ -4448,6 +4448,213 @@
"Flags": 0, "Flags": 0,
"Sequence": 62 "Sequence": 62
} }
},
{
"binary": "12002315000A2200000000240015DAE161400000000000271068400000000000000A6BD5838D7EA4C680000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440B3154D968314FCEB58001E1B0C3A4CFB33DF9FF6C73207E5EAEB9BD07E2747672168E1A2786D950495C38BD8DEE3391BF45F3008DD36F4B12E7C07D82CA5250E8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMCreate",
"TxnSignature": "B3154D968314FCEB58001E1B0C3A4CFB33DF9FF6C73207E5EAEB9BD07E2747672168E1A2786D950495C38BD8DEE3391BF45F3008DD36F4B12E7C07D82CA5250E",
"Amount": "10000",
"Amount2": {
"currency": "ETH",
"issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9",
"value": "10000"
},
"TradingFee": 10,
"Fee": "10",
"Flags": 0,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8"
}
},
{
"binary": "1200242200010000240015DAE168400000000000000A6019D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874408073C588E7EF672DD171E414638D9AF8DBE9A1359E030DE3E1C9AA6A38A2CE9E138CB56482BB844F7228D48B1E4AD7D09BB7E9F639C115958EEEA374749CA00B8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMDeposit",
"TxnSignature": "8073C588E7EF672DD171E414638D9AF8DBE9A1359E030DE3E1C9AA6A38A2CE9E138CB56482BB844F7228D48B1E4AD7D09BB7E9F639C115958EEEA374749CA00B",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"},
"Fee": "10",
"Flags": 65536,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8"
}
},
{
"binary": "1200242200080000240015DAE16140000000000003E868400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744096CA066F42871C55088D2758D64148921B1ACAA5C6C648D0F7D675BBF47F87DF711F17C5BD172666D5AEC257520C587A849A6E063345609D91E121A78816EB048114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMDeposit",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"Amount": "1000",
"Fee": "10",
"Flags": 524288,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
"TxnSignature": "96CA066F42871C55088D2758D64148921B1ACAA5C6C648D0F7D675BBF47F87DF711F17C5BD172666D5AEC257520C587A849A6E063345609D91E121A78816EB04"
}
},
{
"binary": "1200242200100000240015DAE16140000000000003E868400000000000000A6BD511C37937E080000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440FC22B16A098C236ED7EDB3EBC983026DFD218A03C8BAA848F3E1D5389D5B8B00473C1178C5BA257BFA2DCD433C414690A430A5CFD71C1C0A7F7BF725EC1759018114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMDeposit",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"Amount": "1000",
"Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"},
"Fee": "10",
"Flags": 1048576,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
"TxnSignature": "FC22B16A098C236ED7EDB3EBC983026DFD218A03C8BAA848F3E1D5389D5B8B00473C1178C5BA257BFA2DCD433C414690A430A5CFD71C1C0A7F7BF725EC175901"
}
},
{
"binary": "1200242200200000240015DAE16140000000000003E868400000000000000A6019D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440117CF90F9B113AD3BD638B6DB63562B37C287D5180F278B3CCF58FC14A5BAEE98307EA0F6DFE19E2FBA887C92955BA5D1A04F92ADAAEB309DE89C3610D074C098114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMDeposit",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"Amount": "1000",
"LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"},
"Fee": "10",
"Flags": 2097152,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
"TxnSignature": "117CF90F9B113AD3BD638B6DB63562B37C287D5180F278B3CCF58FC14A5BAEE98307EA0F6DFE19E2FBA887C92955BA5D1A04F92ADAAEB309DE89C3610D074C09"
}
},
{
"binary": "1200242200400000240015DAE16140000000000003E868400000000000000A601B40000000000000197321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874405E51EBC6B52A7C3BA5D0AE2FC8F62E779B80182009B3108A87AB6D770D68F56053C193DB0640128E4765565970625B1E2878E116AC854E6DED412202CCDE0B0D8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMDeposit",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"Amount": "1000",
"EPrice": "25",
"Fee": "10",
"Flags": 4194304,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
"TxnSignature": "5E51EBC6B52A7C3BA5D0AE2FC8F62E779B80182009B3108A87AB6D770D68F56053C193DB0640128E4765565970625B1E2878E116AC854E6DED412202CCDE0B0D"
}
},
{
"binary": "1200252200010000240015DAE168400000000000000A601AD5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874409D4F41FC452526C0AD17191959D9B6D04A3C73B3A6C29E0F34C8459675A83A7A7D6E3021390EC8C9BE6C93E11C167E12016465E523F64F9EB3194B0A52E418028114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMWithdraw",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"},
"Fee": "10",
"Flags": 65536,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
"TxnSignature": "9D4F41FC452526C0AD17191959D9B6D04A3C73B3A6C29E0F34C8459675A83A7A7D6E3021390EC8C9BE6C93E11C167E12016465E523F64F9EB3194B0A52E41802"
}
},
{
"binary": "1200252200080000240015DAE16140000000000003E868400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440E2C60D56C337D6D73E4B7D53579C93C666605494E82A89DD58CFDE79E2A4866BCF52370A2146877A2EF748E98168373710001133A51B645D89491849079035018114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMWithdraw",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"Amount": "1000",
"Fee": "10",
"Flags": 524288,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
"TxnSignature": "E2C60D56C337D6D73E4B7D53579C93C666605494E82A89DD58CFDE79E2A4866BCF52370A2146877A2EF748E98168373710001133A51B645D8949184907903501"
}
},
{
"binary": "1200252200100000240015DAE16140000000000003E868400000000000000A6BD511C37937E080000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440D2FCD7D03E53358BC6188BA88A7BA4FF2519B639C3B5C0EBCBDCB704426CA2837111430E92A6003D1CD0D81C63682C74839320539EC4F89B82AA5607714952028114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMWithdraw",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"Amount": "1000",
"Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"},
"Fee": "10",
"Flags": 1048576,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
"TxnSignature": "D2FCD7D03E53358BC6188BA88A7BA4FF2519B639C3B5C0EBCBDCB704426CA2837111430E92A6003D1CD0D81C63682C74839320539EC4F89B82AA560771495202"
}
},
{
"binary": "1200252200200000240015DAE16140000000000003E868400000000000000A601AD5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744042DA5620E924E2D2059BBB4E0C4F03244140ACED93B543136FEEDF802165F814D09F45C7E2A4618468442516F4712A23B1D3332D5DBDBAE830337F39F259C90F8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMWithdraw",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"Amount": "1000",
"LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"},
"Fee": "10",
"Flags": 2097152,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
"TxnSignature": "42DA5620E924E2D2059BBB4E0C4F03244140ACED93B543136FEEDF802165F814D09F45C7E2A4618468442516F4712A23B1D3332D5DBDBAE830337F39F259C90F"
}
},
{
"binary": "1200252200400000240015DAE16140000000000003E868400000000000000A601B40000000000000197321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744045BCEE5A12E5F5F1FB085A24F2F7FD962BBCB0D89A44A5319E3F7E3799E1870341880B6F684132971DDDF2E6B15356B3F407962D6D4E8DE10989F3B16E3CB90D8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMWithdraw",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"Amount": "1000",
"EPrice": "25",
"Fee": "10",
"Flags": 4194304,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
"TxnSignature": "45BCEE5A12E5F5F1FB085A24F2F7FD962BBCB0D89A44A5319E3F7E3799E1870341880B6F684132971DDDF2E6B15356B3F407962D6D4E8DE10989F3B16E3CB90D"
}
},
{
"binary": "1200272200000000240015DAE168400000000000000A6CD4C8E1BC9BF04000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0486DD4CC6F3B40B6C000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440F8EAAFB5EC1A69275167589969F0B9764BACE6BC8CC81482C2FC5ACCE691EDBD0D88D141137B1253BB1B9AC90A8A52CB37F5B6F7E1028B06DD06F91BE06F5A0F8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653AF019E01B81149A91957F8F16BC57F3F200CD8C98375BF1791586E1F10318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMBid",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"AuthAccounts": [{"AuthAccount": {"Account": "rEaHTti4HZsMBpxTAF4ncWxkcdqDh1h6P7"}}],
"BidMax": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "35"},
"BidMin": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "25"},
"Fee": "10",
"Flags": 0,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
"TxnSignature": "F8EAAFB5EC1A69275167589969F0B9764BACE6BC8CC81482C2FC5ACCE691EDBD0D88D141137B1253BB1B9AC90A8A52CB37F5B6F7E1028B06DD06F91BE06F5A0F"
}
},
{
"binary": "1200261500EA2200000000240015DAE168400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440BC2F6E76969E3747E9BDE183C97573B086212F09D5387460E6EE2F32953E85EAEB9618FBBEF077276E30E59D619FCF7C7BDCDDDD9EB94D7CE1DD5CE9246B21078114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C",
"json": {
"Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw",
"TransactionType": "AMMVote",
"Asset": {"currency": "XRP"},
"Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"},
"TradingFee": 234,
"Fee": "10",
"Flags": 0,
"Sequence": 1432289,
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
"TxnSignature": "BC2F6E76969E3747E9BDE183C97573B086212F09D5387460E6EE2F32953E85EAEB9618FBBEF077276E30E59D619FCF7C7BDCDDDD9EB94D7CE1DD5CE9246B2107"
}
}], }],
"ledgerData": [{ "ledgerData": [{
"binary": "01E91435016340767BF1C4A3EACEB081770D8ADE216C85445DD6FB002C6B5A2930F2DECE006DA18150CB18F6DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F873B5C3E520634D343EF5D9D9A4246643D64DAD278BA95DC0EAC6EB5350CF970D521276CDE21276CE60A00", "binary": "01E91435016340767BF1C4A3EACEB081770D8ADE216C85445DD6FB002C6B5A2930F2DECE006DA18150CB18F6DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F873B5C3E520634D343EF5D9D9A4246643D64DAD278BA95DC0EAC6EB5350CF970D521276CDE21276CE60A00",
@@ -4463,4 +4670,4 @@
"transaction_hash": "DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F87" "transaction_hash": "DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F87"
} }
}] }]
} }

View File

@@ -121,6 +121,9 @@ Wallet.fromMmnemonic()
* `Wallet.fromMnemonic` detects when an invalid encoding is provided, and throws an error * `Wallet.fromMnemonic` detects when an invalid encoding is provided, and throws an error
* Made unexpected errors in `submitAndWait` more verbose to make them easier to debug. * Made unexpected errors in `submitAndWait` more verbose to make them easier to debug.
### Added
* Support for Automated Market Maker (AMM) transactions and requests as defined in XLS-30.
## 2.3.1 (2022-06-27) ## 2.3.1 (2022-06-27)
### Fixed ### Fixed
* Signing tx with standard currency codes with lowercase and allowed symbols causing an error on decode. * Signing tx with standard currency codes with lowercase and allowed symbols causing an error on decode.

View File

@@ -95,6 +95,9 @@ import {
NFTInfoResponse, NFTInfoResponse,
NFTHistoryRequest, NFTHistoryRequest,
NFTHistoryResponse, NFTHistoryResponse,
// AMM methods
AMMInfoRequest,
AMMInfoResponse,
} from '../models/methods' } from '../models/methods'
import { BaseRequest, BaseResponse } from '../models/methods/baseMethod' import { BaseRequest, BaseResponse } from '../models/methods/baseMethod'
import { import {
@@ -316,6 +319,7 @@ class Client extends EventEmitter {
): Promise<AccountObjectsResponse> ): Promise<AccountObjectsResponse>
public async request(r: AccountOffersRequest): Promise<AccountOffersResponse> public async request(r: AccountOffersRequest): Promise<AccountOffersResponse>
public async request(r: AccountTxRequest): Promise<AccountTxResponse> public async request(r: AccountTxRequest): Promise<AccountTxResponse>
public async request(r: AMMInfoRequest): Promise<AMMInfoResponse>
public async request(r: BookOffersRequest): Promise<BookOffersResponse> public async request(r: BookOffersRequest): Promise<BookOffersResponse>
public async request(r: ChannelVerifyRequest): Promise<ChannelVerifyResponse> public async request(r: ChannelVerifyRequest): Promise<ChannelVerifyResponse>
public async request( public async request(

View File

@@ -141,3 +141,9 @@ export interface NFToken {
nft_serial: number nft_serial: number
uri: string uri: string
} }
export interface AuthAccount {
AuthAccount: {
account: string
}
}

View File

@@ -0,0 +1,78 @@
import { AuthAccount, Currency } from '../common'
import BaseLedgerEntry from './BaseLedgerEntry'
export interface VoteSlot {
VoteEntry: {
Account: string
TradingFee: number
VoteWeight: number
}
}
/**
* The AMM object type describes a single Automated Market Maker (AMM) instance.
*
* @category Ledger Entries
*/
export default interface AMM extends BaseLedgerEntry {
LedgerEntryType: 'AMM'
/**
* The address of the special account that holds this AMM's assets.
*/
AMMAccount: string
/**
* The definition for one of the two assets this AMM holds.
*/
Asset: Currency
/**
* The definition for the other asset this AMM holds.
*/
Asset2: Currency
/**
* Details of the current owner of the auction slot.
*/
AuctionSlot?: {
/**
* The current owner of this auction slot.
*/
Account: string
/**
* A list of at most 4 additional accounts that are authorized to trade at the discounted fee for this AMM instance.
*/
AuthAccounts?: AuthAccount[]
/**
* The trading fee to be charged to the auction owner, in the same format as TradingFee.
* By default this is 0, meaning that the auction owner can trade at no fee instead of the standard fee for this AMM.
*/
DiscountedFee: number
/**
* The time when this slot expires, in seconds since the Ripple Epoch.
*/
Expiration: number
/**
* The amount the auction owner paid to win this slot, in LP Tokens.
*/
Price: Currency
}
/**
* The total outstanding balance of liquidity provider tokens from this AMM instance.
* The holders of these tokens can vote on the AMM's trading fee in proportion to their holdings,
* or redeem the tokens for a share of the AMM's assets which grows with the trading fees collected.
*/
LPTokenBalance: Currency
/**
* The percentage fee to be charged for trades against this AMM instance, in units of 1/100,000.
* The maximum value is 1000, for a 1% fee.
*/
TradingFee: number
/**
* A list of vote objects, representing votes on the pool's trading fee.
*/
VoteSlots?: VoteSlot[]
/**
* A bit-map of boolean flags. No flags are defined for the AMM object
* type, so this value is always 0.
*/
Flags: 0
}

View File

@@ -126,6 +126,10 @@ export interface AccountRootFlagsInterface {
* (It has DepositAuth enabled.) * (It has DepositAuth enabled.)
*/ */
lsfDepositAuth?: boolean lsfDepositAuth?: boolean
/**
* This account is an Automated Market Maker (AMM) instance.
*/
lsfAMM?: boolean
/** /**
* Disallow incoming NFTOffers from other accounts. * Disallow incoming NFTOffers from other accounts.
*/ */
@@ -186,6 +190,10 @@ export enum AccountRootFlags {
* (It has DepositAuth enabled.) * (It has DepositAuth enabled.)
*/ */
lsfDepositAuth = 0x01000000, lsfDepositAuth = 0x01000000,
/**
* This account is an Automated Market Maker (AMM) instance.
*/
lsfAMM = 0x02000000,
/** /**
* Disallow incoming NFTOffers from other accounts. * Disallow incoming NFTOffers from other accounts.
*/ */

View File

@@ -1,5 +1,6 @@
import AccountRoot from './AccountRoot' import AccountRoot from './AccountRoot'
import Amendments from './Amendments' import Amendments from './Amendments'
import AMM from './AMM'
import Check from './Check' import Check from './Check'
import DepositPreauth from './DepositPreauth' import DepositPreauth from './DepositPreauth'
import DirectoryNode from './DirectoryNode' import DirectoryNode from './DirectoryNode'
@@ -16,6 +17,7 @@ import Ticket from './Ticket'
type LedgerEntry = type LedgerEntry =
| AccountRoot | AccountRoot
| Amendments | Amendments
| AMM
| Check | Check
| DepositPreauth | DepositPreauth
| DirectoryNode | DirectoryNode

View File

@@ -0,0 +1,145 @@
import { Amount, Currency, IssuedCurrencyAmount } from '../common'
import { BaseRequest, BaseResponse } from './baseMethod'
/**
* The `amm_info` method gets information about an Automated Market Maker (AMM) instance.
* Returns an {@link AMMInfoResponse}.
*
* @category Requests
*/
export interface AMMInfoRequest extends BaseRequest {
command: 'amm_info'
/**
* One of the assets of the AMM pool to look up.
*/
asset: Currency
/**
* The other asset of the AMM pool.
*/
asset2: Currency
}
/**
* Response expected from an {@link AMMInfoRequest}.
*
* @category Responses
*/
export interface AMMInfoResponse extends BaseResponse {
result: {
amm: {
/**
* The address of the AMM Account.
*/
account: string
/**
* The total amount of one asset in the AMM's pool.
* (Note: This could be asset or asset2 from the request)
*/
amount: Amount
/**
* The total amount of the other asset in the AMM's pool.
* (Note: This could be asset or asset2 from the request)
*/
amount2: Amount
/**
* (Omitted for XRP) If true, the amount currency is currently frozen for asset.
*/
asset_frozen?: boolean
/**
* (Omitted for XRP) If true, the amount currency is currently frozen for asset2.
*/
asset2_frozen?: boolean
/**
* (May be omitted) An Auction Slot Object describing the current auction slot holder, if there is one.
*/
auction_slot?: {
/**
* The address of the account that owns the auction slot.
*/
account: string
/**
* A list of additional accounts that the auction slot holder has designated as being eligible
* of the discounted trading fee.
* Each member of this array is an object with one field, account, containing the address of the designated account.
*/
auth_accounts: Array<{
account: string
}>
/**
* The discounted trading fee that applies to the auction slot holder, and any eligible accounts
* when trading against this AMM.
* This is always 0.
*/
discounted_fee: number
/**
* The ISO 8601 UTC timestamp after which this auction slot expires.
* After expired, the auction slot does not apply (but the data can remain in the ledger
* until another transaction replaces it or cleans it up).
*/
expiration: string
/**
* The amount, in LP Tokens, that the auction slot holder paid to win the auction slot.
* This affects the price to outbid the current slot holder.
*/
price: Amount
/**
* The current 72-minute time interval this auction slot is in, from 0 to 19.
* The auction slot expires after 24 hours (20 intervals of 72 minutes)
* and affects the cost to outbid the current holder and how much the current holder is refunded if someone outbids them.
*/
time_interval: number
}
/**
* The total amount of this AMM's LP Tokens outstanding.
*/
lp_token: IssuedCurrencyAmount
/**
* The AMM's current trading fee, in units of 1/100,000; a value of 1 is equivalent to a 0.001% fee.
*/
trading_fee: number
/**
* (May be omitted) The current votes for the AMM's trading fee, as Vote Slot Objects.
*/
vote_slots?: Array<{
account: string
trading_fee: number
vote_weight: number
}>
}
/**
* The identifying hash of the ledger that was used to generate this
* response.
*/
ledger_hash?: string
/**
* The ledger index of the ledger version that was used to generate this
* response.
*/
ledger_index?: number
/**
* If included and set to true, the information in this response comes from
* a validated ledger version. Otherwise, the information is subject to
* change.
*/
validated?: boolean
}
}

View File

@@ -41,6 +41,7 @@ import {
AccountTxResponse, AccountTxResponse,
AccountTxTransaction, AccountTxTransaction,
} from './accountTx' } from './accountTx'
import { AMMInfoRequest, AMMInfoResponse } from './ammInfo'
import { import {
BaseRequest, BaseRequest,
BaseResponse, BaseResponse,
@@ -196,6 +197,8 @@ type Request =
// clio only methods // clio only methods
| NFTInfoRequest | NFTInfoRequest
| NFTHistoryRequest | NFTHistoryRequest
// AMM methods
| AMMInfoRequest
/** /**
* @category Responses * @category Responses
@@ -247,6 +250,8 @@ type Response =
// clio only methods // clio only methods
| NFTInfoResponse | NFTInfoResponse
| NFTHistoryResponse | NFTHistoryResponse
// AMM methods
| AMMInfoResponse
export { export {
// Allow users to define their own requests and responses. This is useful for releasing experimental versions // Allow users to define their own requests and responses. This is useful for releasing experimental versions
@@ -380,4 +385,7 @@ export {
NFTHistoryRequest, NFTHistoryRequest,
NFTHistoryResponse, NFTHistoryResponse,
NFTHistoryTransaction, NFTHistoryTransaction,
// AMM methods
AMMInfoRequest,
AMMInfoResponse,
} }

View File

@@ -20,6 +20,21 @@ import { BaseRequest, BaseResponse, LookupByLedgerRequest } from './baseMethod'
*/ */
export interface LedgerEntryRequest extends BaseRequest, LookupByLedgerRequest { export interface LedgerEntryRequest extends BaseRequest, LookupByLedgerRequest {
command: 'ledger_entry' command: 'ledger_entry'
/**
* Retrieve an Automated Market Maker (AMM) object from the ledger.
* This is similar to amm_info method, but the ledger_entry version returns only the ledger entry as stored.
*/
amm?: {
asset: {
currency: string
issuer?: string
}
asset2: {
currency: string
issuer?: string
}
}
/** /**
* If true, return the requested ledger object's contents as a hex string in * If true, return the requested ledger object's contents as a hex string in
* the XRP Ledger's binary format. Otherwise, return data in JSON format. The * the XRP Ledger's binary format. Otherwise, return data in JSON format. The

View File

@@ -0,0 +1,140 @@
/* eslint-disable complexity -- required for validateAMMBid */
import { ValidationError } from '../../errors'
import { Amount, AuthAccount, Currency } from '../common'
import {
BaseTransaction,
isAmount,
isCurrency,
validateBaseTransaction,
} from './common'
const MAX_AUTH_ACCOUNTS = 4
/**
* Bid on an Automated Market Maker's (AMM's) auction slot.
*
* If you win, you can trade against the AMM at a discounted fee until you are outbid or 24 hours have passed.
* If you are outbid before 24 hours have passed, you are refunded part of the cost of your bid based on how much time remains.
* You bid using the AMM's LP Tokens; the amount of a winning bid is returned to the AMM,
* decreasing the outstanding balance of LP Tokens.
*/
export interface AMMBid extends BaseTransaction {
TransactionType: 'AMMBid'
/**
* The definition for one of the assets in the AMM's pool.
*/
Asset: Currency
/**
* The definition for the other asset in the AMM's pool.
*/
Asset2: Currency
/**
* Pay at least this amount for the slot.
* Setting this value higher makes it harder for others to outbid you.
* If omitted, pay the minimum necessary to win the bid.
*/
BidMin?: Amount
/**
* Pay at most this amount for the slot.
* If the cost to win the bid is higher than this amount, the transaction fails.
* If omitted, pay as much as necessary to win the bid.
*/
BidMax?: Amount
/**
* A list of up to 4 additional accounts that you allow to trade at the discounted fee.
* This cannot include the address of the transaction sender.
*/
AuthAccounts?: AuthAccount[]
}
/**
* Verify the form and type of an AMMBid at runtime.
*
* @param tx - An AMMBid Transaction.
* @throws When the AMMBid is Malformed.
*/
export function validateAMMBid(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Asset == null) {
throw new ValidationError('AMMBid: missing field Asset')
}
if (!isCurrency(tx.Asset)) {
throw new ValidationError('AMMBid: Asset must be a Currency')
}
if (tx.Asset2 == null) {
throw new ValidationError('AMMBid: missing field Asset2')
}
if (!isCurrency(tx.Asset2)) {
throw new ValidationError('AMMBid: Asset2 must be a Currency')
}
if (tx.BidMin != null && !isAmount(tx.BidMin)) {
throw new ValidationError('AMMBid: BidMin must be an Amount')
}
if (tx.BidMax != null && !isAmount(tx.BidMax)) {
throw new ValidationError('AMMBid: BidMax must be an Amount')
}
if (tx.AuthAccounts != null) {
if (!Array.isArray(tx.AuthAccounts)) {
throw new ValidationError(
`AMMBid: AuthAccounts must be an AuthAccount array`,
)
}
if (tx.AuthAccounts.length > MAX_AUTH_ACCOUNTS) {
throw new ValidationError(
`AMMBid: AuthAccounts length must not be greater than ${MAX_AUTH_ACCOUNTS}`,
)
}
validateAuthAccounts(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Only used by JS
tx.Account as string,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Only used by JS
tx.AuthAccounts as Array<Record<string, unknown>>,
)
}
}
function validateAuthAccounts(
senderAddress: string,
authAccounts: Array<Record<string, unknown>>,
): boolean {
for (const authAccount of authAccounts) {
if (
authAccount.AuthAccount == null ||
typeof authAccount.AuthAccount !== 'object'
) {
throw new ValidationError(`AMMBid: invalid AuthAccounts`)
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- used for null check
// @ts-expect-error -- used for null check
if (authAccount.AuthAccount.Account == null) {
throw new ValidationError(`AMMBid: invalid AuthAccounts`)
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- used for null check
// @ts-expect-error -- used for null check
if (typeof authAccount.AuthAccount.Account !== 'string') {
throw new ValidationError(`AMMBid: invalid AuthAccounts`)
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- used for null check
// @ts-expect-error -- used for null check
if (authAccount.AuthAccount.Account === senderAddress) {
throw new ValidationError(
`AMMBid: AuthAccounts must not include sender's address`,
)
}
}
return true
}

View File

@@ -0,0 +1,80 @@
import { ValidationError } from '../../errors'
import { Amount } from '../common'
import { BaseTransaction, isAmount, validateBaseTransaction } from './common'
export const AMM_MAX_TRADING_FEE = 1000
/**
* Create a new Automated Market Maker (AMM) instance for trading a pair of assets (fungible tokens or XRP).
*
* Creates both an AMM object and a special AccountRoot object to represent the AMM.
* Also transfers ownership of the starting balance of both assets from the sender to the created AccountRoot
* and issues an initial balance of liquidity provider tokens (LP Tokens) from the AMM account to the sender.
*
* CAUTION: When you create the AMM, you should fund it with (approximately) equal-value amounts of each asset.
* Otherwise, other users can profit at your expense by trading with this AMM (performing arbitrage).
* The currency risk that liquidity providers take on increases with the volatility (potential for imbalance) of the asset pair.
* The higher the trading fee, the more it offsets this risk,
* so it's best to set the trading fee based on the volatility of the asset pair.
*/
export interface AMMCreate extends BaseTransaction {
TransactionType: 'AMMCreate'
/**
* The first of the two assets to fund this AMM with. This must be a positive amount.
*/
Amount: Amount
/**
* The second of the two assets to fund this AMM with. This must be a positive amount.
*/
Amount2: Amount
/**
* The fee to charge for trades against this AMM instance, in units of 1/100,000; a value of 1 is equivalent to 0.001%.
* The maximum value is 1000, indicating a 1% fee.
* The minimum value is 0.
*/
TradingFee: number
}
/**
* Verify the form and type of an AMMCreate at runtime.
*
* @param tx - An AMMCreate Transaction.
* @throws When the AMMCreate is Malformed.
*/
export function validateAMMCreate(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Amount == null) {
throw new ValidationError('AMMCreate: missing field Amount')
}
if (!isAmount(tx.Amount)) {
throw new ValidationError('AMMCreate: Amount must be an Amount')
}
if (tx.Amount2 == null) {
throw new ValidationError('AMMCreate: missing field Amount2')
}
if (!isAmount(tx.Amount2)) {
throw new ValidationError('AMMCreate: Amount2 must be an Amount')
}
if (tx.TradingFee == null) {
throw new ValidationError('AMMCreate: missing field TradingFee')
}
if (typeof tx.TradingFee !== 'number') {
throw new ValidationError('AMMCreate: TradingFee must be a number')
}
if (tx.TradingFee < 0 || tx.TradingFee > AMM_MAX_TRADING_FEE) {
throw new ValidationError(
`AMMCreate: TradingFee must be between 0 and ${AMM_MAX_TRADING_FEE}`,
)
}
}

View File

@@ -0,0 +1,55 @@
import { ValidationError } from '../../errors'
import { Currency } from '../common'
import { BaseTransaction, isCurrency, validateBaseTransaction } from './common'
/**
* Delete an empty Automated Market Maker (AMM) instance that could not be fully deleted automatically.
*
* Tip: The AMMWithdraw transaction automatically tries to delete an AMM, along with associated ledger
* entries such as empty trust lines, if it withdrew all the assets from the AMM's pool.
* However, if there are too many trust lines to the AMM account to remove in one transaction,
* it may stop before fully removing the AMM. Similarly, an AMMDelete transaction removes up to
* a maximum number of trust lines; in extreme cases, it may take several AMMDelete transactions
* to fully delete the trust lines and the associated AMM.
* In all cases, the AMM ledger entry and AMM account are deleted by the last such transaction.
*/
export interface AMMDelete extends BaseTransaction {
TransactionType: 'AMMDelete'
/**
* The definition for one of the assets in the AMM's pool.
*/
Asset: Currency
/**
* The definition for the other asset in the AMM's pool.
*/
Asset2: Currency
}
/**
* Verify the form and type of an AMMDelete at runtime.
*
* @param tx - An AMMDelete Transaction.
* @throws When the AMMDelete is Malformed.
*/
export function validateAMMDelete(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Asset == null) {
throw new ValidationError('AMMDelete: missing field Asset')
}
if (!isCurrency(tx.Asset)) {
throw new ValidationError('AMMDelete: Asset must be a Currency')
}
if (tx.Asset2 == null) {
throw new ValidationError('AMMDelete: missing field Asset2')
}
if (!isCurrency(tx.Asset2)) {
throw new ValidationError('AMMDelete: Asset2 must be a Currency')
}
}

View File

@@ -0,0 +1,130 @@
/* eslint-disable complexity -- required for validateAMMDeposit */
import { ValidationError } from '../../errors'
import { Amount, Currency, IssuedCurrencyAmount } from '../common'
import {
BaseTransaction,
GlobalFlags,
isAmount,
isCurrency,
isIssuedCurrency,
validateBaseTransaction,
} from './common'
/**
* Enum representing values for AMMDeposit Transaction Flags.
*
* @category Transaction Flags
*/
export enum AMMDepositFlags {
tfLPToken = 0x00010000,
tfSingleAsset = 0x00080000,
tfTwoAsset = 0x00100000,
tfOneAssetLPToken = 0x00200000,
tfLimitLPToken = 0x00400000,
}
export interface AMMDepositFlagsInterface extends GlobalFlags {
tfLPToken?: boolean
tfSingleAsset?: boolean
tfTwoAsset?: boolean
tfOneAssetLPToken?: boolean
tfLimitLPToken?: boolean
}
/**
* Deposit funds into an Automated Market Maker (AMM) instance
* and receive the AMM's liquidity provider tokens (LP Tokens) in exchange.
*
* You can deposit one or both of the assets in the AMM's pool.
* If successful, this transaction creates a trust line to the AMM Account (limit 0) to hold the LP Tokens.
*/
export interface AMMDeposit extends BaseTransaction {
TransactionType: 'AMMDeposit'
/**
* The definition for one of the assets in the AMM's pool.
*/
Asset: Currency
/**
* The definition for the other asset in the AMM's pool.
*/
Asset2: Currency
/**
* The amount of one asset to deposit to the AMM.
* If present, this must match the type of one of the assets (tokens or XRP) in the AMM's pool.
*/
Amount?: Amount
/**
* The amount of another asset to add to the AMM.
* If present, this must match the type of the other asset in the AMM's pool and cannot be the same asset as Amount.
*/
Amount2?: Amount
/**
* The maximum effective price, in the deposit asset, to pay for each LP Token received.
*/
EPrice?: Amount
/**
* How many of the AMM's LP Tokens to buy.
*/
LPTokenOut?: IssuedCurrencyAmount
}
/**
* Verify the form and type of an AMMDeposit at runtime.
*
* @param tx - An AMMDeposit Transaction.
* @throws When the AMMDeposit is Malformed.
*/
export function validateAMMDeposit(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Asset == null) {
throw new ValidationError('AMMDeposit: missing field Asset')
}
if (!isCurrency(tx.Asset)) {
throw new ValidationError('AMMDeposit: Asset must be a Currency')
}
if (tx.Asset2 == null) {
throw new ValidationError('AMMDeposit: missing field Asset2')
}
if (!isCurrency(tx.Asset2)) {
throw new ValidationError('AMMDeposit: Asset2 must be a Currency')
}
if (tx.Amount2 != null && tx.Amount == null) {
throw new ValidationError('AMMDeposit: must set Amount with Amount2')
} else if (tx.EPrice != null && tx.Amount == null) {
throw new ValidationError('AMMDeposit: must set Amount with EPrice')
} else if (tx.LPTokenOut == null && tx.Amount == null) {
throw new ValidationError(
'AMMDeposit: must set at least LPTokenOut or Amount',
)
}
if (tx.LPTokenOut != null && !isIssuedCurrency(tx.LPTokenOut)) {
throw new ValidationError(
'AMMDeposit: LPTokenOut must be an IssuedCurrencyAmount',
)
}
if (tx.Amount != null && !isAmount(tx.Amount)) {
throw new ValidationError('AMMDeposit: Amount must be an Amount')
}
if (tx.Amount2 != null && !isAmount(tx.Amount2)) {
throw new ValidationError('AMMDeposit: Amount2 must be an Amount')
}
if (tx.EPrice != null && !isAmount(tx.EPrice)) {
throw new ValidationError('AMMDeposit: EPrice must be an Amount')
}
}

View File

@@ -0,0 +1,71 @@
import { ValidationError } from '../../errors'
import { Currency } from '../common'
import { AMM_MAX_TRADING_FEE } from './AMMCreate'
import { BaseTransaction, isCurrency, validateBaseTransaction } from './common'
/**
* Vote on the trading fee for an Automated Market Maker (AMM) instance.
*
* Up to 8 accounts can vote in proportion to the amount of the AMM's LP Tokens they hold.
* Each new vote re-calculates the AMM's trading fee based on a weighted average of the votes.
*/
export interface AMMVote extends BaseTransaction {
TransactionType: 'AMMVote'
/**
* The definition for one of the assets in the AMM's pool.
*/
Asset: Currency
/**
* The definition for the other asset in the AMM's pool.
*/
Asset2: Currency
/**
* The proposed fee to vote for, in units of 1/100,000; a value of 1 is equivalent to 0.001%.
* The maximum value is 1000, indicating a 1% fee.
*/
TradingFee: number
}
/**
* Verify the form and type of an AMMVote at runtime.
*
* @param tx - An AMMVote Transaction.
* @throws When the AMMVote is Malformed.
*/
export function validateAMMVote(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Asset == null) {
throw new ValidationError('AMMVote: missing field Asset')
}
if (!isCurrency(tx.Asset)) {
throw new ValidationError('AMMVote: Asset must be a Currency')
}
if (tx.Asset2 == null) {
throw new ValidationError('AMMVote: missing field Asset2')
}
if (!isCurrency(tx.Asset2)) {
throw new ValidationError('AMMVote: Asset2 must be a Currency')
}
if (tx.TradingFee == null) {
throw new ValidationError('AMMVote: missing field TradingFee')
}
if (typeof tx.TradingFee !== 'number') {
throw new ValidationError('AMMVote: TradingFee must be a number')
}
if (tx.TradingFee < 0 || tx.TradingFee > AMM_MAX_TRADING_FEE) {
throw new ValidationError(
`AMMVote: TradingFee must be between 0 and ${AMM_MAX_TRADING_FEE}`,
)
}
}

View File

@@ -0,0 +1,126 @@
/* eslint-disable complexity -- required for validateAMMWithdraw */
import { ValidationError } from '../../errors'
import { Amount, Currency, IssuedCurrencyAmount } from '../common'
import {
BaseTransaction,
GlobalFlags,
isAmount,
isCurrency,
isIssuedCurrency,
validateBaseTransaction,
} from './common'
/**
* Enum representing values for AMMWithdrawFlags Transaction Flags.
*
* @category Transaction Flags
*/
export enum AMMWithdrawFlags {
tfLPToken = 0x00010000,
tfWithdrawAll = 0x00020000,
tfOneAssetWithdrawAll = 0x00040000,
tfSingleAsset = 0x00080000,
tfTwoAsset = 0x00100000,
tfOneAssetLPToken = 0x00200000,
tfLimitLPToken = 0x00400000,
}
export interface AMMWithdrawFlagsInterface extends GlobalFlags {
tfLPToken?: boolean
tfWithdrawAll?: boolean
tfOneAssetWithdrawAll?: boolean
tfSingleAsset?: boolean
tfTwoAsset?: boolean
tfOneAssetLPToken?: boolean
tfLimitLPToken?: boolean
}
/**
* Withdraw assets from an Automated Market Maker (AMM) instance by returning the AMM's liquidity provider tokens (LP Tokens).
*/
export interface AMMWithdraw extends BaseTransaction {
TransactionType: 'AMMWithdraw'
/**
* The definition for one of the assets in the AMM's pool.
*/
Asset: Currency
/**
* The definition for the other asset in the AMM's pool.
*/
Asset2: Currency
/**
* The amount of one asset to withdraw from the AMM.
* This must match the type of one of the assets (tokens or XRP) in the AMM's pool.
*/
Amount?: Amount
/**
* The amount of another asset to withdraw from the AMM.
* If present, this must match the type of the other asset in the AMM's pool and cannot be the same type as Amount.
*/
Amount2?: Amount
/**
* The minimum effective price, in LP Token returned, to pay per unit of the asset to withdraw.
*/
EPrice?: Amount
/**
* How many of the AMM's LP Tokens to redeem.
*/
LPTokenIn?: IssuedCurrencyAmount
}
/**
* Verify the form and type of an AMMWithdraw at runtime.
*
* @param tx - An AMMWithdraw Transaction.
* @throws When the AMMWithdraw is Malformed.
*/
export function validateAMMWithdraw(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Asset == null) {
throw new ValidationError('AMMWithdraw: missing field Asset')
}
if (!isCurrency(tx.Asset)) {
throw new ValidationError('AMMWithdraw: Asset must be a Currency')
}
if (tx.Asset2 == null) {
throw new ValidationError('AMMWithdraw: missing field Asset2')
}
if (!isCurrency(tx.Asset2)) {
throw new ValidationError('AMMWithdraw: Asset2 must be a Currency')
}
if (tx.Amount2 != null && tx.Amount == null) {
throw new ValidationError('AMMWithdraw: must set Amount with Amount2')
} else if (tx.EPrice != null && tx.Amount == null) {
throw new ValidationError('AMMWithdraw: must set Amount with EPrice')
}
if (tx.LPTokenIn != null && !isIssuedCurrency(tx.LPTokenIn)) {
throw new ValidationError(
'AMMWithdraw: LPTokenIn must be an IssuedCurrencyAmount',
)
}
if (tx.Amount != null && !isAmount(tx.Amount)) {
throw new ValidationError('AMMWithdraw: Amount must be an Amount')
}
if (tx.Amount2 != null && !isAmount(tx.Amount2)) {
throw new ValidationError('AMMWithdraw: Amount2 must be an Amount')
}
if (tx.EPrice != null && !isAmount(tx.EPrice)) {
throw new ValidationError('AMMWithdraw: EPrice must be an Amount')
}
}

View File

@@ -4,7 +4,7 @@
import { TRANSACTION_TYPES } from 'ripple-binary-codec' import { TRANSACTION_TYPES } from 'ripple-binary-codec'
import { ValidationError } from '../../errors' import { ValidationError } from '../../errors'
import { Amount, IssuedCurrencyAmount, Memo, Signer } from '../common' import { Amount, Currency, IssuedCurrencyAmount, Memo, Signer } from '../common'
import { onlyHasFields } from '../utils' import { onlyHasFields } from '../utils'
const MEMO_SIZE = 3 const MEMO_SIZE = 3
@@ -50,17 +50,36 @@ function isSigner(obj: unknown): boolean {
) )
} }
const XRP_CURRENCY_SIZE = 1
const ISSUE_SIZE = 2
const ISSUED_CURRENCY_SIZE = 3 const ISSUED_CURRENCY_SIZE = 3
function isRecord(value: unknown): value is Record<string, unknown> { function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object' return value !== null && typeof value === 'object'
} }
/**
* Verify the form and type of an IssuedCurrency at runtime.
*
* @param input - The input to check the form and type of.
* @returns Whether the IssuedCurrency is properly formed.
*/
export function isCurrency(input: unknown): input is Currency {
return (
isRecord(input) &&
((Object.keys(input).length === ISSUE_SIZE &&
typeof input.issuer === 'string' &&
typeof input.currency === 'string') ||
(Object.keys(input).length === XRP_CURRENCY_SIZE &&
input.currency === 'XRP'))
)
}
/** /**
* Verify the form and type of an IssuedCurrencyAmount at runtime. * Verify the form and type of an IssuedCurrencyAmount at runtime.
* *
* @param input - The input to check the form and type of. * @param input - The input to check the form and type of.
* @returns Whether the IssuedCurrencyAmount is malformed. * @returns Whether the IssuedCurrencyAmount is properly formed.
*/ */
export function isIssuedCurrency( export function isIssuedCurrency(
input: unknown, input: unknown,
@@ -78,7 +97,7 @@ export function isIssuedCurrency(
* Verify the form and type of an Amount at runtime. * Verify the form and type of an Amount at runtime.
* *
* @param amount - The object to check the form and type of. * @param amount - The object to check the form and type of.
* @returns Whether the Amount is malformed. * @returns Whether the Amount is properly formed.
*/ */
export function isAmount(amount: unknown): amount is Amount { export function isAmount(amount: unknown): amount is Amount {
return typeof amount === 'string' || isIssuedCurrency(amount) return typeof amount === 'string' || isIssuedCurrency(amount)

View File

@@ -8,6 +8,20 @@ export {
AccountSet, AccountSet,
} from './accountSet' } from './accountSet'
export { AccountDelete } from './accountDelete' export { AccountDelete } from './accountDelete'
export { AMMBid } from './AMMBid'
export { AMMDelete } from './AMMDelete'
export {
AMMDepositFlags,
AMMDepositFlagsInterface,
AMMDeposit,
} from './AMMDeposit'
export { AMMCreate } from './AMMCreate'
export { AMMVote } from './AMMVote'
export {
AMMWithdrawFlags,
AMMWithdrawFlagsInterface,
AMMWithdraw,
} from './AMMWithdraw'
export { CheckCancel } from './checkCancel' export { CheckCancel } from './checkCancel'
export { CheckCash } from './checkCash' export { CheckCash } from './checkCash'
export { CheckCreate } from './checkCreate' export { CheckCreate } from './checkCreate'

View File

@@ -8,6 +8,12 @@ import { setTransactionFlagsToNumber } from '../utils/flags'
import { AccountDelete, validateAccountDelete } from './accountDelete' import { AccountDelete, validateAccountDelete } from './accountDelete'
import { AccountSet, validateAccountSet } from './accountSet' import { AccountSet, validateAccountSet } from './accountSet'
import { AMMBid, validateAMMBid } from './AMMBid'
import { AMMCreate, validateAMMCreate } from './AMMCreate'
import { AMMDelete, validateAMMDelete } from './AMMDelete'
import { AMMDeposit, validateAMMDeposit } from './AMMDeposit'
import { AMMVote, validateAMMVote } from './AMMVote'
import { AMMWithdraw, validateAMMWithdraw } from './AMMWithdraw'
import { CheckCancel, validateCheckCancel } from './checkCancel' import { CheckCancel, validateCheckCancel } from './checkCancel'
import { CheckCash, validateCheckCash } from './checkCash' import { CheckCash, validateCheckCash } from './checkCash'
import { CheckCreate, validateCheckCreate } from './checkCreate' import { CheckCreate, validateCheckCreate } from './checkCreate'
@@ -58,6 +64,12 @@ import { TrustSet, validateTrustSet } from './trustSet'
export type Transaction = export type Transaction =
| AccountDelete | AccountDelete
| AccountSet | AccountSet
| AMMBid
| AMMDelete
| AMMDeposit
| AMMCreate
| AMMVote
| AMMWithdraw
| CheckCancel | CheckCancel
| CheckCash | CheckCash
| CheckCreate | CheckCreate
@@ -167,6 +179,30 @@ export function validate(transaction: Record<string, unknown>): void {
validateAccountSet(tx) validateAccountSet(tx)
break break
case 'AMMBid':
validateAMMBid(tx)
break
case 'AMMDelete':
validateAMMDelete(tx)
break
case 'AMMDeposit':
validateAMMDeposit(tx)
break
case 'AMMCreate':
validateAMMCreate(tx)
break
case 'AMMVote':
validateAMMVote(tx)
break
case 'AMMWithdraw':
validateAMMWithdraw(tx)
break
case 'CheckCancel': case 'CheckCancel':
validateCheckCancel(tx) validateCheckCancel(tx)
break break

View File

@@ -6,22 +6,15 @@ import {
AccountRootFlagsInterface, AccountRootFlagsInterface,
AccountRootFlags, AccountRootFlags,
} from '../ledger/AccountRoot' } from '../ledger/AccountRoot'
import { import { AccountSetTfFlags } from '../transactions/accountSet'
AccountSetFlagsInterface, import { AMMDepositFlags } from '../transactions/AMMDeposit'
AccountSetTfFlags, import { AMMWithdrawFlags } from '../transactions/AMMWithdraw'
} from '../transactions/accountSet'
import { GlobalFlags } from '../transactions/common' import { GlobalFlags } from '../transactions/common'
import { import { OfferCreateFlags } from '../transactions/offerCreate'
OfferCreateFlagsInterface, import { PaymentFlags } from '../transactions/payment'
OfferCreateFlags, import { PaymentChannelClaimFlags } from '../transactions/paymentChannelClaim'
} from '../transactions/offerCreate'
import { PaymentFlagsInterface, PaymentFlags } from '../transactions/payment'
import {
PaymentChannelClaimFlagsInterface,
PaymentChannelClaimFlags,
} from '../transactions/paymentChannelClaim'
import type { Transaction } from '../transactions/transaction' import type { Transaction } from '../transactions/transaction'
import { TrustSetFlagsInterface, TrustSetFlags } from '../transactions/trustSet' import { TrustSetFlags } from '../transactions/trustSet'
import { isFlagEnabled } from '.' import { isFlagEnabled } from '.'
@@ -65,55 +58,33 @@ export function setTransactionFlagsToNumber(tx: Transaction): void {
switch (tx.TransactionType) { switch (tx.TransactionType) {
case 'AccountSet': case 'AccountSet':
tx.Flags = convertAccountSetFlagsToNumber(tx.Flags) tx.Flags = convertFlagsToNumber(tx.Flags, AccountSetTfFlags)
return
case 'AMMDeposit':
tx.Flags = convertFlagsToNumber(tx.Flags, AMMDepositFlags)
return
case 'AMMWithdraw':
tx.Flags = convertFlagsToNumber(tx.Flags, AMMWithdrawFlags)
return return
case 'OfferCreate': case 'OfferCreate':
tx.Flags = convertOfferCreateFlagsToNumber(tx.Flags) tx.Flags = convertFlagsToNumber(tx.Flags, OfferCreateFlags)
return return
case 'PaymentChannelClaim': case 'PaymentChannelClaim':
tx.Flags = convertPaymentChannelClaimFlagsToNumber(tx.Flags) tx.Flags = convertFlagsToNumber(tx.Flags, PaymentChannelClaimFlags)
return return
case 'Payment': case 'Payment':
tx.Flags = convertPaymentTransactionFlagsToNumber(tx.Flags) tx.Flags = convertFlagsToNumber(tx.Flags, PaymentFlags)
return return
case 'TrustSet': case 'TrustSet':
tx.Flags = convertTrustSetFlagsToNumber(tx.Flags) tx.Flags = convertFlagsToNumber(tx.Flags, TrustSetFlags)
return return
default: default:
tx.Flags = 0 tx.Flags = 0
} }
} }
function convertAccountSetFlagsToNumber(
flags: AccountSetFlagsInterface,
): number {
return reduceFlags(flags, AccountSetTfFlags)
}
function convertOfferCreateFlagsToNumber(
flags: OfferCreateFlagsInterface,
): number {
return reduceFlags(flags, OfferCreateFlags)
}
function convertPaymentChannelClaimFlagsToNumber(
flags: PaymentChannelClaimFlagsInterface,
): number {
return reduceFlags(flags, PaymentChannelClaimFlags)
}
function convertPaymentTransactionFlagsToNumber(
flags: PaymentFlagsInterface,
): number {
return reduceFlags(flags, PaymentFlags)
}
function convertTrustSetFlagsToNumber(flags: TrustSetFlagsInterface): number {
return reduceFlags(flags, TrustSetFlags)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- added ValidationError check for flagEnum // eslint-disable-next-line @typescript-eslint/no-explicit-any -- added ValidationError check for flagEnum
function reduceFlags(flags: GlobalFlags, flagEnum: any): number { function convertFlagsToNumber(flags: GlobalFlags, flagEnum: any): number {
return Object.keys(flags).reduce((resultFlags, flag) => { return Object.keys(flags).reduce((resultFlags, flag) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
if (flagEnum[flag] == null) { if (flagEnum[flag] == null) {

View File

@@ -275,7 +275,7 @@ async function setNextValidSequenceNumber(
tx.Sequence = data.result.account_data.Sequence tx.Sequence = data.result.account_data.Sequence
} }
async function fetchAccountDeleteFee(client: Client): Promise<BigNumber> { async function fetchOwnerReserveFee(client: Client): Promise<BigNumber> {
const response = await client.request({ command: 'server_state' }) const response = await client.request({ command: 'server_state' })
const fee = response.result.state.validated_ledger?.reserve_inc const fee = response.result.state.validated_ledger?.reserve_inc
@@ -307,9 +307,11 @@ async function calculateFeePerTransactionType(
baseFee = product.dp(0, BigNumber.ROUND_CEIL) baseFee = product.dp(0, BigNumber.ROUND_CEIL)
} }
// AccountDelete Transaction if (
if (tx.TransactionType === 'AccountDelete') { tx.TransactionType === 'AccountDelete' ||
baseFee = await fetchAccountDeleteFee(client) tx.TransactionType === 'AMMCreate'
) {
baseFee = await fetchOwnerReserveFee(client)
} }
/* /*

View File

@@ -0,0 +1,168 @@
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import { validateAMMBid } from '../../src/models/transactions/AMMBid'
/**
* AMMBid Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('AMMBid', function () {
let bid
beforeEach(function () {
bid = {
TransactionType: 'AMMBid',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
Asset: {
currency: 'XRP',
},
Asset2: {
currency: 'ETH',
issuer: 'rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd',
},
BidMin: '5',
BidMax: '10',
AuthAccounts: [
{
AuthAccount: {
Account: 'rNZdsTBP5tH1M6GHC6bTreHAp6ouP8iZSh',
},
},
{
AuthAccount: {
Account: 'rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH',
},
},
{
AuthAccount: {
Account: 'rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb',
},
},
{
AuthAccount: {
Account: 'rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4',
},
},
],
Sequence: 1337,
} as any
})
it(`verifies valid AMMBid`, function () {
assert.doesNotThrow(() => validateAMMBid(bid))
assert.doesNotThrow(() => validate(bid))
})
it(`throws w/ missing field Asset`, function () {
delete bid.Asset
const errorMessage = 'AMMBid: missing field Asset'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ Asset must be a Currency`, function () {
bid.Asset = 1234
const errorMessage = 'AMMBid: Asset must be a Currency'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ missing field Asset2`, function () {
delete bid.Asset2
const errorMessage = 'AMMBid: missing field Asset2'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ Asset2 must be a Currency`, function () {
bid.Asset2 = 1234
const errorMessage = 'AMMBid: Asset2 must be a Currency'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ BidMin must be an Amount`, function () {
bid.BidMin = 5
const errorMessage = 'AMMBid: BidMin must be an Amount'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ BidMax must be an Amount`, function () {
bid.BidMax = 10
const errorMessage = 'AMMBid: BidMax must be an Amount'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ AuthAccounts length must not be greater than 4`, function () {
bid.AuthAccounts.push({
AuthAccount: {
Account: 'r3X6noRsvaLapAKCG78zAtWcbhB3sggS1s',
},
})
const errorMessage =
'AMMBid: AuthAccounts length must not be greater than 4'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ AuthAccounts must be an AuthAccount array`, function () {
bid.AuthAccounts = 1234
const errorMessage = 'AMMBid: AuthAccounts must be an AuthAccount array'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ invalid AuthAccounts when AuthAccount is null`, function () {
bid.AuthAccounts[0] = {
AuthAccount: null,
}
const errorMessage = 'AMMBid: invalid AuthAccounts'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ invalid AuthAccounts when AuthAccount is undefined`, function () {
bid.AuthAccounts[0] = {
AuthAccount: undefined,
}
const errorMessage = 'AMMBid: invalid AuthAccounts'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ invalid AuthAccounts when AuthAccount is not an object`, function () {
bid.AuthAccounts[0] = {
AuthAccount: 1234,
}
const errorMessage = 'AMMBid: invalid AuthAccounts'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ invalid AuthAccounts when AuthAccount.Account is not a string`, function () {
bid.AuthAccounts[0] = {
AuthAccount: {
Account: 1234,
},
}
const errorMessage = 'AMMBid: invalid AuthAccounts'
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
it(`throws w/ AuthAccounts must not include sender's address`, function () {
bid.AuthAccounts[0] = {
AuthAccount: {
Account: bid.Account,
},
}
const errorMessage =
"AMMBid: AuthAccounts must not include sender's address"
assert.throws(() => validateAMMBid(bid), ValidationError, errorMessage)
assert.throws(() => validate(bid), ValidationError, errorMessage)
})
})

View File

@@ -0,0 +1,121 @@
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import { validateAMMCreate } from '../../src/models/transactions/AMMCreate'
/**
* AMMCreate Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('AMMCreate', function () {
let ammCreate
beforeEach(function () {
ammCreate = {
TransactionType: 'AMMCreate',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
Amount: '1000',
Amount2: {
currency: 'USD',
issuer: 'rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9',
value: '1000',
},
TradingFee: 12,
Sequence: 1337,
} as any
})
it(`verifies valid AMMCreate`, function () {
assert.doesNotThrow(() => validateAMMCreate(ammCreate))
assert.doesNotThrow(() => validate(ammCreate))
})
it(`throws w/ missing Amount`, function () {
delete ammCreate.Amount
const errorMessage = 'AMMCreate: missing field Amount'
assert.throws(
() => validateAMMCreate(ammCreate),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammCreate), ValidationError, errorMessage)
})
it(`throws w/ Amount must be an Amount`, function () {
ammCreate.Amount = 1000
const errorMessage = 'AMMCreate: Amount must be an Amount'
assert.throws(
() => validateAMMCreate(ammCreate),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammCreate), ValidationError, errorMessage)
})
it(`throws w/ missing Amount2`, function () {
delete ammCreate.Amount2
const errorMessage = 'AMMCreate: missing field Amount2'
assert.throws(
() => validateAMMCreate(ammCreate),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammCreate), ValidationError, errorMessage)
})
it(`throws w/ Amount2 must be an Amount`, function () {
ammCreate.Amount2 = 1000
const errorMessage = 'AMMCreate: Amount2 must be an Amount'
assert.throws(
() => validateAMMCreate(ammCreate),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammCreate), ValidationError, errorMessage)
})
it(`throws w/ missing TradingFee`, function () {
delete ammCreate.TradingFee
const errorMessage = 'AMMCreate: missing field TradingFee'
assert.throws(
() => validateAMMCreate(ammCreate),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammCreate), ValidationError, errorMessage)
})
it(`throws w/ TradingFee must be a number`, function () {
ammCreate.TradingFee = '12'
const errorMessage = 'AMMCreate: TradingFee must be a number'
assert.throws(
() => validateAMMCreate(ammCreate),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammCreate), ValidationError, errorMessage)
})
it(`throws when TradingFee is greater than 1000`, function () {
ammCreate.TradingFee = 1001
const errorMessage = 'AMMCreate: TradingFee must be between 0 and 1000'
assert.throws(
() => validateAMMCreate(ammCreate),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammCreate), ValidationError, errorMessage)
})
it(`throws when TradingFee is a negative number`, function () {
ammCreate.TradingFee = -1
const errorMessage = 'AMMCreate: TradingFee must be between 0 and 1000'
assert.throws(
() => validateAMMCreate(ammCreate),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammCreate), ValidationError, errorMessage)
})
})

View File

@@ -0,0 +1,78 @@
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import { validateAMMDelete } from '../../src/models/transactions/AMMDelete'
/**
* AMMDelete Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('AMMDelete', function () {
let ammDelete
beforeEach(function () {
ammDelete = {
TransactionType: 'AMMDelete',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
Asset: {
currency: 'XRP',
},
Asset2: {
currency: 'ETH',
issuer: 'rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd',
},
Sequence: 1337,
Flags: 0,
} as any
})
it(`verifies valid AMMDelete`, function () {
assert.doesNotThrow(() => validateAMMDelete(ammDelete))
assert.doesNotThrow(() => validate(ammDelete))
})
it(`throws w/ missing field Asset`, function () {
delete ammDelete.Asset
const errorMessage = 'AMMDelete: missing field Asset'
assert.throws(
() => validateAMMDelete(ammDelete),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammDelete), ValidationError, errorMessage)
})
it(`throws w/ Asset must be a Currency`, function () {
ammDelete.Asset = 1234
const errorMessage = 'AMMDelete: Asset must be a Currency'
assert.throws(
() => validateAMMDelete(ammDelete),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammDelete), ValidationError, errorMessage)
})
it(`throws w/ missing field Asset2`, function () {
delete ammDelete.Asset2
const errorMessage = 'AMMDelete: missing field Asset2'
assert.throws(
() => validateAMMDelete(ammDelete),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammDelete), ValidationError, errorMessage)
})
it(`throws w/ Asset2 must be a Currency`, function () {
ammDelete.Asset2 = 1234
const errorMessage = 'AMMDelete: Asset2 must be a Currency'
assert.throws(
() => validateAMMDelete(ammDelete),
ValidationError,
errorMessage,
)
assert.throws(() => validate(ammDelete), ValidationError, errorMessage)
})
})

View File

@@ -0,0 +1,204 @@
/* eslint-disable no-bitwise -- bitwise necessary for enabling flags */
import { assert } from 'chai'
import { AMMDepositFlags, validate, ValidationError } from '../../src'
import { validateAMMDeposit } from '../../src/models/transactions/AMMDeposit'
/**
* AMMDeposit Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('AMMDeposit', function () {
const LPTokenOut = {
currency: 'B3813FCAB4EE68B3D0D735D6849465A9113EE048',
issuer: 'rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg',
value: '1000',
}
let deposit
beforeEach(function () {
deposit = {
TransactionType: 'AMMDeposit',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
Asset: {
currency: 'XRP',
},
Asset2: {
currency: 'ETH',
issuer: 'rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd',
},
Sequence: 1337,
Flags: 0,
} as any
})
it(`verifies valid AMMDeposit with LPTokenOut`, function () {
deposit.LPTokenOut = LPTokenOut
deposit.Flags |= AMMDepositFlags.tfLPToken
assert.doesNotThrow(() => validateAMMDeposit(deposit))
assert.doesNotThrow(() => validate(deposit))
})
it(`verifies valid AMMDeposit with Amount`, function () {
deposit.Amount = '1000'
deposit.Flags |= AMMDepositFlags.tfSingleAsset
assert.doesNotThrow(() => validateAMMDeposit(deposit))
assert.doesNotThrow(() => validate(deposit))
})
it(`verifies valid AMMDeposit with Amount and Amount2`, function () {
deposit.Amount = '1000'
deposit.Amount2 = {
currency: 'ETH',
issuer: 'rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd',
value: '2.5',
}
deposit.Flags |= AMMDepositFlags.tfTwoAsset
assert.doesNotThrow(() => validateAMMDeposit(deposit))
assert.doesNotThrow(() => validate(deposit))
})
it(`verifies valid AMMDeposit with Amount and LPTokenOut`, function () {
deposit.Amount = '1000'
deposit.LPTokenOut = LPTokenOut
deposit.Flags |= AMMDepositFlags.tfOneAssetLPToken
assert.doesNotThrow(() => validateAMMDeposit(deposit))
assert.doesNotThrow(() => validate(deposit))
})
it(`verifies valid AMMDeposit with Amount and EPrice`, function () {
deposit.Amount = '1000'
deposit.EPrice = '25'
deposit.Flags |= AMMDepositFlags.tfLimitLPToken
assert.doesNotThrow(() => validateAMMDeposit(deposit))
assert.doesNotThrow(() => validate(deposit))
})
it(`throws w/ missing field Asset`, function () {
delete deposit.Asset
const errorMessage = 'AMMDeposit: missing field Asset'
assert.throws(
() => validateAMMDeposit(deposit),
ValidationError,
errorMessage,
)
assert.throws(() => validate(deposit), ValidationError, errorMessage)
})
it(`throws w/ Asset must be a Currency`, function () {
deposit.Asset = 1234
const errorMessage = 'AMMDeposit: Asset must be a Currency'
assert.throws(
() => validateAMMDeposit(deposit),
ValidationError,
errorMessage,
)
assert.throws(() => validate(deposit), ValidationError, errorMessage)
})
it(`throws w/ missing field Asset2`, function () {
delete deposit.Asset2
const errorMessage = 'AMMDeposit: missing field Asset2'
assert.throws(
() => validateAMMDeposit(deposit),
ValidationError,
errorMessage,
)
assert.throws(() => validate(deposit), ValidationError, errorMessage)
})
it(`throws w/ Asset2 must be a Currency`, function () {
deposit.Asset2 = 1234
const errorMessage = 'AMMDeposit: Asset2 must be a Currency'
assert.throws(
() => validateAMMDeposit(deposit),
ValidationError,
errorMessage,
)
assert.throws(() => validate(deposit), ValidationError, errorMessage)
})
it(`throws w/ must set at least LPTokenOut or Amount`, function () {
const errorMessage = 'AMMDeposit: must set at least LPTokenOut or Amount'
assert.throws(
() => validateAMMDeposit(deposit),
ValidationError,
errorMessage,
)
assert.throws(() => validate(deposit), ValidationError, errorMessage)
})
it(`throws w/ must set Amount with Amount2`, function () {
deposit.Amount2 = {
currency: 'ETH',
issuer: 'rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd',
value: '2.5',
}
const errorMessage = 'AMMDeposit: must set Amount with Amount2'
assert.throws(
() => validateAMMDeposit(deposit),
ValidationError,
errorMessage,
)
assert.throws(() => validate(deposit), ValidationError, errorMessage)
})
it(`throws w/ must set Amount with EPrice`, function () {
deposit.EPrice = '25'
const errorMessage = 'AMMDeposit: must set Amount with EPrice'
assert.throws(
() => validateAMMDeposit(deposit),
ValidationError,
errorMessage,
)
assert.throws(() => validate(deposit), ValidationError, errorMessage)
})
it(`throws w/ LPTokenOut must be an IssuedCurrencyAmount`, function () {
deposit.LPTokenOut = 1234
const errorMessage =
'AMMDeposit: LPTokenOut must be an IssuedCurrencyAmount'
assert.throws(
() => validateAMMDeposit(deposit),
ValidationError,
errorMessage,
)
assert.throws(() => validate(deposit), ValidationError, errorMessage)
})
it(`throws w/ Amount must be an Amount`, function () {
deposit.Amount = 1234
const errorMessage = 'AMMDeposit: Amount must be an Amount'
assert.throws(
() => validateAMMDeposit(deposit),
ValidationError,
errorMessage,
)
assert.throws(() => validate(deposit), ValidationError, errorMessage)
})
it(`throws w/ Amount2 must be an Amount`, function () {
deposit.Amount = '1000'
deposit.Amount2 = 1234
const errorMessage = 'AMMDeposit: Amount2 must be an Amount'
assert.throws(
() => validateAMMDeposit(deposit),
ValidationError,
errorMessage,
)
assert.throws(() => validate(deposit), ValidationError, errorMessage)
})
it(`throws w/ EPrice must be an Amount`, function () {
deposit.Amount = '1000'
deposit.EPrice = 1234
const errorMessage = 'AMMDeposit: EPrice must be an Amount'
assert.throws(
() => validateAMMDeposit(deposit),
ValidationError,
errorMessage,
)
assert.throws(() => validate(deposit), ValidationError, errorMessage)
})
})

View File

@@ -0,0 +1,90 @@
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import { validateAMMVote } from '../../src/models/transactions/AMMVote'
/**
* AMMVote Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('AMMVote', function () {
let vote
beforeEach(function () {
vote = {
TransactionType: 'AMMVote',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
Asset: {
currency: 'XRP',
},
Asset2: {
currency: 'ETH',
issuer: 'rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd',
},
TradingFee: 25,
Sequence: 1337,
} as any
})
it(`verifies valid AMMVote`, function () {
assert.doesNotThrow(() => validateAMMVote(vote))
assert.doesNotThrow(() => validate(vote))
})
it(`throws w/ missing field Asset`, function () {
delete vote.Asset
const errorMessage = 'AMMVote: missing field Asset'
assert.throws(() => validateAMMVote(vote), ValidationError, errorMessage)
assert.throws(() => validate(vote), ValidationError, errorMessage)
})
it(`throws w/ Asset must be a Currency`, function () {
vote.Asset = 1234
const errorMessage = 'AMMVote: Asset must be a Currency'
assert.throws(() => validateAMMVote(vote), ValidationError, errorMessage)
assert.throws(() => validate(vote), ValidationError, errorMessage)
})
it(`throws w/ missing field Asset2`, function () {
delete vote.Asset2
const errorMessage = 'AMMVote: missing field Asset2'
assert.throws(() => validateAMMVote(vote), ValidationError, errorMessage)
assert.throws(() => validate(vote), ValidationError, errorMessage)
})
it(`throws w/ Asset2 must be a Currency`, function () {
vote.Asset2 = 1234
const errorMessage = 'AMMVote: Asset2 must be a Currency'
assert.throws(() => validateAMMVote(vote), ValidationError, errorMessage)
assert.throws(() => validate(vote), ValidationError, errorMessage)
})
it(`throws w/ missing field TradingFee`, function () {
delete vote.TradingFee
const errorMessage = 'AMMVote: missing field TradingFee'
assert.throws(() => validateAMMVote(vote), ValidationError, errorMessage)
assert.throws(() => validate(vote), ValidationError, errorMessage)
})
it(`throws w/ TradingFee must be a number`, function () {
vote.TradingFee = '25'
const errorMessage = 'AMMVote: TradingFee must be a number'
assert.throws(() => validateAMMVote(vote), ValidationError, errorMessage)
assert.throws(() => validate(vote), ValidationError, errorMessage)
})
it(`throws when TradingFee is greater than AMM_MAX_TRADING_FEE`, function () {
vote.TradingFee = 1001
const errorMessage = 'AMMVote: TradingFee must be between 0 and 1000'
assert.throws(() => validateAMMVote(vote), ValidationError, errorMessage)
assert.throws(() => validate(vote), ValidationError, errorMessage)
})
it(`throws when TradingFee is a negative number`, function () {
vote.TradingFee = -1
const errorMessage = 'AMMVote: TradingFee must be between 0 and 1000'
assert.throws(() => validateAMMVote(vote), ValidationError, errorMessage)
assert.throws(() => validate(vote), ValidationError, errorMessage)
})
})

View File

@@ -0,0 +1,207 @@
/* eslint-disable no-bitwise -- bitwise necessary for enabling flags */
import { assert } from 'chai'
import { AMMWithdrawFlags, validate, ValidationError } from '../../src'
import { validateAMMWithdraw } from '../../src/models/transactions/AMMWithdraw'
/**
* AMMWithdraw Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('AMMWithdraw', function () {
const LPTokenIn = {
currency: 'B3813FCAB4EE68B3D0D735D6849465A9113EE048',
issuer: 'rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg',
value: '1000',
}
let withdraw
beforeEach(function () {
withdraw = {
TransactionType: 'AMMWithdraw',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
Asset: {
currency: 'XRP',
},
Asset2: {
currency: 'ETH',
issuer: 'rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd',
},
Sequence: 1337,
Flags: 0,
} as any
})
it(`verifies valid AMMWithdraw with LPTokenIn`, function () {
withdraw.LPTokenIn = LPTokenIn
withdraw.Flags |= AMMWithdrawFlags.tfLPToken
assert.doesNotThrow(() => validateAMMWithdraw(withdraw))
assert.doesNotThrow(() => validate(withdraw))
})
it(`verifies valid AMMWithdraw with Amount`, function () {
withdraw.Amount = '1000'
withdraw.Flags |= AMMWithdrawFlags.tfSingleAsset
assert.doesNotThrow(() => validateAMMWithdraw(withdraw))
assert.doesNotThrow(() => validate(withdraw))
})
it(`verifies valid AMMWithdraw with Amount and Amount2`, function () {
withdraw.Amount = '1000'
withdraw.Amount2 = {
currency: 'ETH',
issuer: 'rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd',
value: '2.5',
}
withdraw.Flags |= AMMWithdrawFlags.tfTwoAsset
assert.doesNotThrow(() => validateAMMWithdraw(withdraw))
assert.doesNotThrow(() => validate(withdraw))
})
it(`verifies valid AMMWithdraw with Amount and LPTokenIn`, function () {
withdraw.Amount = '1000'
withdraw.LPTokenIn = LPTokenIn
withdraw.Flags |= AMMWithdrawFlags.tfOneAssetLPToken
assert.doesNotThrow(() => validateAMMWithdraw(withdraw))
assert.doesNotThrow(() => validate(withdraw))
})
it(`verifies valid AMMWithdraw with Amount and EPrice`, function () {
withdraw.Amount = '1000'
withdraw.EPrice = '25'
withdraw.Flags |= AMMWithdrawFlags.tfLimitLPToken
assert.doesNotThrow(() => validateAMMWithdraw(withdraw))
assert.doesNotThrow(() => validate(withdraw))
})
it(`verifies valid AMMWithdraw one asset withdraw all`, function () {
withdraw.Amount = '1000'
withdraw.Flags |= AMMWithdrawFlags.tfOneAssetWithdrawAll
assert.doesNotThrow(() => validateAMMWithdraw(withdraw))
assert.doesNotThrow(() => validate(withdraw))
})
it(`verifies valid AMMWithdraw withdraw all`, function () {
withdraw.Flags |= AMMWithdrawFlags.tfWithdrawAll
assert.doesNotThrow(() => validateAMMWithdraw(withdraw))
assert.doesNotThrow(() => validate(withdraw))
})
it(`throws w/ missing field Asset`, function () {
delete withdraw.Asset
const errorMessage = 'AMMWithdraw: missing field Asset'
assert.throws(
() => validateAMMWithdraw(withdraw),
ValidationError,
errorMessage,
)
assert.throws(() => validate(withdraw), ValidationError, errorMessage)
})
it(`throws w/ Asset must be a Currency`, function () {
withdraw.Asset = 1234
const errorMessage = 'AMMWithdraw: Asset must be a Currency'
assert.throws(
() => validateAMMWithdraw(withdraw),
ValidationError,
errorMessage,
)
assert.throws(() => validate(withdraw), ValidationError, errorMessage)
})
it(`throws w/ missing field Asset2`, function () {
delete withdraw.Asset2
const errorMessage = 'AMMWithdraw: missing field Asset2'
assert.throws(
() => validateAMMWithdraw(withdraw),
ValidationError,
errorMessage,
)
assert.throws(() => validate(withdraw), ValidationError, errorMessage)
})
it(`throws w/ Asset2 must be a Currency`, function () {
withdraw.Asset2 = 1234
const errorMessage = 'AMMWithdraw: Asset2 must be a Currency'
assert.throws(
() => validateAMMWithdraw(withdraw),
ValidationError,
errorMessage,
)
assert.throws(() => validate(withdraw), ValidationError, errorMessage)
})
it(`throws w/ must set Amount with Amount2`, function () {
withdraw.Amount2 = {
currency: 'ETH',
issuer: 'rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd',
value: '2.5',
}
const errorMessage = 'AMMWithdraw: must set Amount with Amount2'
assert.throws(
() => validateAMMWithdraw(withdraw),
ValidationError,
errorMessage,
)
assert.throws(() => validate(withdraw), ValidationError, errorMessage)
})
it(`throws w/ must set Amount with EPrice`, function () {
withdraw.EPrice = '25'
const errorMessage = 'AMMWithdraw: must set Amount with EPrice'
assert.throws(
() => validateAMMWithdraw(withdraw),
ValidationError,
errorMessage,
)
assert.throws(() => validate(withdraw), ValidationError, errorMessage)
})
it(`throws w/ LPTokenIn must be an IssuedCurrencyAmount`, function () {
withdraw.LPTokenIn = 1234
const errorMessage =
'AMMWithdraw: LPTokenIn must be an IssuedCurrencyAmount'
assert.throws(
() => validateAMMWithdraw(withdraw),
ValidationError,
errorMessage,
)
assert.throws(() => validate(withdraw), ValidationError, errorMessage)
})
it(`throws w/ Amount must be an Amount`, function () {
withdraw.Amount = 1234
const errorMessage = 'AMMWithdraw: Amount must be an Amount'
assert.throws(
() => validateAMMWithdraw(withdraw),
ValidationError,
errorMessage,
)
assert.throws(() => validate(withdraw), ValidationError, errorMessage)
})
it(`throws w/ Amount2 must be an Amount`, function () {
withdraw.Amount = '1000'
withdraw.Amount2 = 1234
const errorMessage = 'AMMWithdraw: Amount2 must be an Amount'
assert.throws(
() => validateAMMWithdraw(withdraw),
ValidationError,
errorMessage,
)
assert.throws(() => validate(withdraw), ValidationError, errorMessage)
})
it(`throws w/ EPrice must be an Amount`, function () {
withdraw.Amount = '1000'
withdraw.EPrice = 1234
const errorMessage = 'AMMWithdraw: EPrice must be an Amount'
assert.throws(
() => validateAMMWithdraw(withdraw),
ValidationError,
errorMessage,
)
assert.throws(() => validate(withdraw), ValidationError, errorMessage)
})
})