Compare commits

...

21 Commits

Author SHA1 Message Date
Mayukha Vadari
dc6e164a3c remove federator_info 2022-06-14 17:45:45 -04:00
Mayukha Vadari
49a6e1d5c0 stop running tests on prepare 2022-06-14 17:45:45 -04:00
Mayukha Vadari
b7a35a44e6 fix ci install again 2022-06-14 17:45:45 -04:00
Mayukha Vadari
8b4026dd3c fix installs 2022-06-14 17:45:45 -04:00
Mayukha Vadari
d440917bbd back out change 2022-06-14 17:45:45 -04:00
Mayukha Vadari
f07d47c4bf fix types 2022-06-14 17:45:45 -04:00
Mayukha Vadari
d6df030eaa clean up 2022-06-14 17:45:45 -04:00
Mayukha Vadari
b3056890c8 more matching to rippled 2022-06-14 17:45:45 -04:00
Mayukha Vadari
15b2522bfa improve eslint error 2022-06-14 17:45:45 -04:00
Mayukha Vadari
a88af970e3 fix tests 2022-06-14 17:45:45 -04:00
Mayukha Vadari
d1f560a786 match rippled implementation 2022-06-14 17:45:45 -04:00
Mayukha Vadari
6488c4172e add transactions to validate list 2022-06-14 17:45:45 -04:00
Mayukha Vadari
40827abc60 fix comment 2022-06-14 17:45:45 -04:00
Mayukha Vadari
ac5956e75b remove old tests 2022-06-14 17:45:45 -04:00
Mayukha Vadari
8a0615d808 first draft of new transaction types 2022-06-14 17:45:45 -04:00
Mayukha Vadari
b38b9fffbe edit changelog 2022-06-14 17:45:44 -04:00
Mayukha Vadari
fad3949fc4 add support for XChainClaim 2022-06-14 17:44:56 -04:00
Mayukha Vadari
1b82ee19e2 add XChainSeqNumCreate, XChainTransfer test cases 2022-06-14 17:44:56 -04:00
Mayukha Vadari
f717990922 get testcase passing 2022-06-14 17:44:56 -04:00
Mayukha Vadari
158cff2be2 first pass 2022-06-14 17:44:55 -04:00
Mayukha Vadari
37117d9bff remove old design stuff 2022-06-14 17:44:55 -04:00
28 changed files with 897 additions and 266 deletions

View File

@@ -1,10 +1,13 @@
# ripple-binary-codec Release History
## Unreleased
### Added
- Support for sidechain transactions
## 1.4.1 (2022-06-02)
### Fixed
- Added a clearer error message for trying to encode an invalid transaction. (Ex. With an incorrect TransactionType)
## 1.4.0 (2022-04-18)
- Updated NFT definitions to match 1.9.0's breaking naming changes

View File

@@ -24,9 +24,10 @@
"build:default": "tsc -b && copy .\\src\\enums\\definitions.json .\\dist\\enums",
"build:nix": "tsc -b && cp ./src/enums/definitions.json ./dist/enums",
"clean": "rm -rf ./dist && rm -rf tsconfig.tsbuildinfo",
"prepare": "npm run build && npm test",
"prepare": "npm run build",
"test": "jest",
"lint": "eslint . --ext .ts --ext .test.js"
"lint": "eslint . --ext .ts --ext .test.js",
"prepublishOnly": "npm run build && npm run test"
},
"repository": {
"type": "git",

View File

@@ -19,7 +19,9 @@
"NotPresent": 0,
"UInt64": 3,
"UInt32": 2,
"STArray": 15
"STArray": 15,
"Sidechain": 24,
"XChainClaimProof": 25
},
"LEDGER_ENTRY_TYPES": {
"Any": -3,
@@ -31,8 +33,10 @@
"Ticket": 84,
"SignerList": 83,
"Offer": 111,
"Sidechain": 105,
"LedgerHashes": 104,
"Amendments": 102,
"CrosschainSequenceNum": 113,
"FeeSettings": 115,
"Escrow": 117,
"PayChannel": 120,
@@ -825,6 +829,16 @@
"type": "Amount"
}
],
[
"XChainFee",
{
"nth": 11,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Amount"
}
],
[
"MinimumOffer",
{
@@ -1175,6 +1189,26 @@
"type": "AccountID"
}
],
[
"ThisChainAccount",
{
"nth": 17,
"isVLEncoded": true,
"isSerialized": true,
"isSigningField": true,
"type": "AccountID"
}
],
[
"OtherChainAccount",
{
"nth": 18,
"isVLEncoded": true,
"isSerialized": true,
"isSigningField": true,
"type": "AccountID"
}
],
[
"ObjectEndMarker",
{
@@ -1325,6 +1359,16 @@
"type": "STObject"
}
],
[
"XChainProofSig",
{
"nth": 25,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STObject"
}
],
[
"ArrayEndMarker",
{
@@ -1435,6 +1479,16 @@
"type": "STArray"
}
],
[
"XChainProofSigs",
{
"nth": 21,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STArray"
}
],
[
"CloseResolution",
{
@@ -1515,6 +1569,26 @@
"type": "PathSet"
}
],
[
"Sidechain",
{
"nth": 1,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Sidechain"
}
],
[
"XChainClaimProof",
{
"nth": 1,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "XChainClaimProof"
}
],
[
"Indexes",
{
@@ -1655,6 +1729,16 @@
"type": "UInt32"
}
],
[
"XChainSequence",
{
"nth": 47,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "UInt32"
}
],
[
"Channel",
{
@@ -1848,6 +1932,9 @@
"temUNKNOWN": -265,
"temSEQ_AND_TICKET": -264,
"temBAD_NFTOKEN_TRANSFER_FEE": -263,
"temEQUAL_DOOR_ACCOUNTS": -262,
"temBAD_XChain_Proof": -261,
"temSIDECHAIN_BAD_ISSUES": -260,
"tefFAILURE": -199,
"tefALREADY": -198,
@@ -1933,8 +2020,14 @@
"tecINSUFFICIENT_FUNDS": 159,
"tecOBJECT_NOT_FOUND": 160,
"tecINSUFFICIENT_PAYMENT": 161,
"tecINCORRECT_ASSET": 162,
"tecTOO_MANY": 163
"tecBAD_XCHAIN_TRANSFER_ISSUE": 162,
"tecBAD_XCHAIN_TRANSFER_SEQ_NUM": 163,
"tecXCHAIN_PROOF_NO_QUORUM": 164,
"tecXCHAIN_PROOF_UNKNOWN_KEY": 165,
"tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 166,
"tecXCHAIN_CLAIM_ACCOUNT_DST_EXISTS": 167,
"tecXCHAIN_CLAIM_WRONG_CHAIN": 168
},
"TRANSACTION_TYPES": {
"Invalid": -1,
@@ -1966,6 +2059,13 @@
"NFTokenCreateOffer": 27,
"NFTokenCancelOffer": 28,
"NFTokenAcceptOffer": 29,
"XChainDoorCreate": 30,
"XChainSeqNumCreate": 31,
"XChainTransfer": 32,
"XChainClaim": 33,
"XChainAccountCreate": 34,
"XChainAccountClaim": 35,
"EnableAmendment": 100,
"SetFee": 101,
"UNLModify": 102

View File

@@ -147,7 +147,8 @@ class Amount extends SerializedType {
*/
toJSON(): AmountObject | string {
if (this.isNative()) {
const bytes = this.bytes
const bytes = Buffer.alloc(this.bytes.length)
this.bytes.copy(bytes)
const isPositive = bytes[0] & 0x40
const sign = isPositive ? '' : '-'
bytes[0] &= 0x3f

View File

@@ -11,7 +11,9 @@ import { Currency } from './currency'
import { Hash128 } from './hash-128'
import { Hash160 } from './hash-160'
import { Hash256 } from './hash-256'
import { IssuedCurrency } from './issued-currency'
import { PathSet } from './path-set'
import { Sidechain } from './sidechain'
import { STArray } from './st-array'
import { STObject } from './st-object'
import { UInt16 } from './uint-16'
@@ -19,6 +21,7 @@ import { UInt32 } from './uint-32'
import { UInt64 } from './uint-64'
import { UInt8 } from './uint-8'
import { Vector256 } from './vector-256'
import { XChainClaimProof } from './xchain-claim-proof'
const coreTypes = {
AccountID,
@@ -28,7 +31,9 @@ const coreTypes = {
Hash128,
Hash160,
Hash256,
IssuedCurrency,
PathSet,
Sidechain,
STArray,
STObject,
UInt8,
@@ -36,6 +41,7 @@ const coreTypes = {
UInt32,
UInt64,
Vector256,
XChainClaimProof,
}
Object.values(Field).forEach((field) => {

View File

@@ -0,0 +1,114 @@
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 IssuedCurrencyObject extends JsonObject {
currency: string
issuer: string
}
/**
* Type guard for AmountObject
*/
function isIssuedCurrencyObject(arg): arg is IssuedCurrencyObject {
const keys = Object.keys(arg).sort()
return keys.length === 2 && keys[0] === 'currency' && keys[1] === 'issuer'
}
/**
* Class for serializing/Deserializing Amounts
*/
class IssuedCurrency extends SerializedType {
static readonly ZERO_ISSUED_CURRENCY: IssuedCurrency = new IssuedCurrency(
Buffer.alloc(20),
)
constructor(bytes: Buffer) {
super(bytes ?? IssuedCurrency.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 IssuedCurrency | IssuedCurrencyObject | string>(
value: T,
): IssuedCurrency {
if (value instanceof IssuedCurrency) {
return value
}
if (typeof value === 'string') {
IssuedCurrency.assertXrpIsValid(value)
const currency = Currency.from(value).toBytes()
return new IssuedCurrency(currency)
}
if (isIssuedCurrencyObject(value)) {
const currency = Currency.from(value.currency).toBytes()
const issuer = AccountID.from(value.issuer).toBytes()
return new IssuedCurrency(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): IssuedCurrency {
const currency = parser.read(20)
if (new Currency(currency).toJSON() === 'XRP') {
return new IssuedCurrency(currency)
}
const currencyAndIssuer = [currency, parser.read(20)]
return new IssuedCurrency(Buffer.concat(currencyAndIssuer))
}
/**
* Get the JSON representation of this Amount
*
* @returns the JSON interpretation of this.bytes
*/
toJSON(): IssuedCurrencyObject | string {
const parser = new BinaryParser(this.toString())
const currency = Currency.fromParser(parser) as Currency
if (currency.toJSON() === 'XRP') {
return currency.toJSON()
}
const issuer = AccountID.fromParser(parser) as AccountID
return {
currency: currency.toJSON(),
issuer: issuer.toJSON(),
}
}
/**
* Validate XRP amount
*
* @param value String representing XRP amount
* @returns void, but will throw if invalid amount
*/
private static assertXrpIsValid(value: string): void {
if (value !== 'XRP') {
throw new Error(`${value} is an illegal amount`)
}
}
}
export { IssuedCurrency, IssuedCurrencyObject }

View File

@@ -0,0 +1,109 @@
import { BinaryParser } from '../serdes/binary-parser'
import { AccountID } from './account-id'
import { JsonObject, SerializedType } from './serialized-type'
import { Buffer } from 'buffer/'
import { IssuedCurrency, IssuedCurrencyObject } from './issued-currency'
/**
* Interface for JSON objects that represent sidechains
*/
interface SidechainObject extends JsonObject {
dst_chain_door: string
dst_chain_issue: IssuedCurrencyObject | string
src_chain_door: string
src_chain_issue: IssuedCurrencyObject | string
}
/**
* Type guard for SidechainObject
*/
function isSidechainObject(arg): arg is SidechainObject {
const keys = Object.keys(arg).sort()
return (
keys.length === 4 &&
keys[0] === 'dst_chain_door' &&
keys[1] === 'dst_chain_issue' &&
keys[2] === 'src_chain_door' &&
keys[3] === 'src_chain_issue'
)
}
/**
* Class for serializing/deserializing Sidechains
*/
class Sidechain extends SerializedType {
static readonly ZERO_SIDECHAIN: Sidechain = new Sidechain(Buffer.alloc(80))
static readonly TYPE_ORDER: { name: string; type: typeof SerializedType }[] =
[
{ name: 'src_chain_door', type: AccountID },
{ name: 'src_chain_issue', type: IssuedCurrency },
{ name: 'dst_chain_door', type: AccountID },
{ name: 'dst_chain_issue', type: IssuedCurrency },
]
constructor(bytes: Buffer) {
super(bytes ?? Sidechain.ZERO_SIDECHAIN.bytes)
}
/**
* Construct a sidechain from a JSON
*
* @param value Sidechain or JSON to parse into a Sidechain
* @returns A Sidechain object
*/
static from<T extends Sidechain | SidechainObject>(value: T): Sidechain {
if (value instanceof Sidechain) {
return value
}
if (isSidechainObject(value)) {
const bytes: Array<Buffer> = []
this.TYPE_ORDER.forEach((item) => {
const { name, type } = item
const object = type.from(value[name])
bytes.push(object.toBytes())
})
return new Sidechain(Buffer.concat(bytes))
}
throw new Error('Invalid type to construct a Sidechain')
}
/**
* Read a Sidechain from a BinaryParser
*
* @param parser BinaryParser to read the Sidechain from
* @returns A Sidechain object
*/
static fromParser(parser: BinaryParser): Sidechain {
const bytes: Array<Buffer> = []
this.TYPE_ORDER.forEach((item) => {
const { type } = item
const object = type.fromParser(parser)
bytes.push(object.toBytes())
})
return new Sidechain(Buffer.concat(bytes))
}
/**
* Get the JSON representation of this Sidechain
*
* @returns the JSON interpretation of this.bytes
*/
toJSON(): SidechainObject {
const parser = new BinaryParser(this.toString())
const json = {}
Sidechain.TYPE_ORDER.forEach((item) => {
const { name, type } = item
const object = type.fromParser(parser).toJSON()
json[name] = object
})
return json as SidechainObject
}
}
export { Sidechain, SidechainObject }

View File

@@ -6,7 +6,7 @@ import { Buffer } from 'buffer/'
* Derived UInt class for serializing/deserializing 32 bit UInt
*/
class UInt32 extends UInt {
protected static readonly width: number = 32 / 8 // 4
public static readonly width: number = 32 / 8 // 4
static readonly defaultUInt32: UInt32 = new UInt32(Buffer.alloc(UInt32.width))
constructor(bytes: Buffer) {

View File

@@ -6,7 +6,7 @@ import { Buffer } from 'buffer/'
* Derived UInt class for serializing/deserializing 8 bit UInt
*/
class UInt8 extends UInt {
protected static readonly width: number = 8 / 8 // 1
public static readonly width: number = 8 / 8 // 1
static readonly defaultUInt8: UInt8 = new UInt8(Buffer.alloc(UInt8.width))
constructor(bytes: Buffer) {
@@ -33,6 +33,12 @@ class UInt8 extends UInt {
return new UInt8(buf)
}
if (typeof val === 'boolean') {
const buf = Buffer.alloc(UInt8.width)
buf.writeUInt8(Number(val), 0)
return new UInt8(buf)
}
throw new Error('Cannot construct UInt8 from given value')
}

View File

@@ -0,0 +1,136 @@
import { BinaryParser } from '../serdes/binary-parser'
import { JsonObject, SerializedType } from './serialized-type'
import { Buffer } from 'buffer/'
import { Sidechain, SidechainObject } from './sidechain'
import { Amount } from './amount'
import { UInt8 } from './uint-8'
import { UInt32 } from './uint-32'
import { STArray } from './st-array'
/**
* Interface for JSON objects that represent signatures
*/
interface SignatureObject extends JsonObject {
XChainProofSig: {
Signature: string
PublicKey: string
}
}
/**
* Interface for JSON objects that represent XChainClaimProofs
*/
interface XChainClaimProofObject extends JsonObject {
amount: string
sidechain: SidechainObject
signatures: SignatureObject[]
was_src_chain_send: boolean
xchain_seq: number
}
/**
* Type guard for XChainClaimProofObject
*/
function isProofObject(arg): arg is XChainClaimProofObject {
const keys = Object.keys(arg).sort()
return (
keys.length === 5 &&
keys[0] === 'amount' &&
keys[1] === 'sidechain' &&
keys[2] === 'signatures' &&
keys[3] === 'was_src_chain_send' &&
keys[4] === 'xchain_seq'
)
}
/**
* Class for serializing/Deserializing XChainClaimProofs
*/
class XChainClaimProof extends SerializedType {
static readonly ZERO_PROOF: XChainClaimProof = new XChainClaimProof(
Buffer.concat([
Sidechain.ZERO_SIDECHAIN.toBytes(),
Amount.defaultAmount.toBytes(),
UInt32.defaultUInt32.toBytes(),
UInt8.defaultUInt8.toBytes(),
]),
)
static readonly TYPE_ORDER: { name: string; type: typeof SerializedType }[] =
[
{ name: 'sidechain', type: Sidechain },
{ name: 'amount', type: Amount },
{ name: 'xchain_seq', type: UInt32 },
{ name: 'was_src_chain_send', type: UInt8 },
{ name: 'signatures', type: STArray },
]
constructor(bytes: Buffer) {
super(bytes ?? XChainClaimProof.ZERO_PROOF.bytes)
}
/**
* Construct a XChainClaimProof from a JSON
*
* @param value XChainClaimProof or JSON to parse into an XChainClaimProof
* @returns A XChainClaimProof object
*/
static from<T extends XChainClaimProof | XChainClaimProofObject>(
value: T,
): XChainClaimProof {
if (value instanceof XChainClaimProof) {
return value
}
if (isProofObject(value)) {
const bytes: Array<Buffer> = []
this.TYPE_ORDER.forEach((item) => {
const { name, type } = item
const object = type.from(value[name])
bytes.push(object.toBytes())
})
return new XChainClaimProof(Buffer.concat(bytes))
}
throw new Error('Invalid type to construct a XChainClaimProof')
}
/**
* Read a XChainClaimProof from a BinaryParser
*
* @param parser BinaryParser to read the XChainClaimProof from
* @returns A XChainClaimProof object
*/
static fromParser(parser: BinaryParser): XChainClaimProof {
const bytes: Array<Buffer> = []
this.TYPE_ORDER.forEach((item) => {
const { type } = item
const object = type.fromParser(parser)
bytes.push(object.toBytes())
})
return new XChainClaimProof(Buffer.concat(bytes))
}
/**
* Get the JSON representation of this XChainClaimProof
*
* @returns the JSON interpretation of this.bytes
*/
toJSON(): XChainClaimProofObject {
const parser = new BinaryParser(this.toString())
const json = {}
XChainClaimProof.TYPE_ORDER.forEach((item) => {
const { name, type } = item
const object = type.fromParser(parser).toJSON()
json[name] = object
})
json['was_src_chain_send'] = Boolean(json['was_src_chain_send'])
return json as XChainClaimProofObject
}
}
export { XChainClaimProof, XChainClaimProofObject }

View File

@@ -105,6 +105,10 @@ let json_omitted = {
}
const NegativeUNL = require('./fixtures/negative-unl.json')
const XChainDoorCreate = require('./fixtures/xchain-door-create.json')
const XChainSeqNumCreate = require('./fixtures/xchain-seqnum-create.json')
const XChainTransfer = require('./fixtures/xchain-transfer.json')
const XChainClaim = require('./fixtures/xchain-claim.json')
function bytesListTest() {
const list = new BytesList()
@@ -220,7 +224,7 @@ function PaymentChannelTest() {
})
}
function NegativeUNLTest() {
function negativeUNLTest() {
test('can serialize NegativeUNL', () => {
expect(encode(NegativeUNL.tx)).toEqual(NegativeUNL.binary)
})
@@ -229,6 +233,33 @@ function NegativeUNLTest() {
})
}
function sidechainTest() {
test('can serialize XChainDoorCreate', () => {
expect(encode(XChainDoorCreate.tx)).toEqual(XChainDoorCreate.binary)
})
test('can deserialize XChainDoorCreate', () => {
expect(decode(XChainDoorCreate.binary)).toEqual(XChainDoorCreate.tx)
})
test('can serialize XChainSeqNumCreate', () => {
expect(encode(XChainSeqNumCreate.tx)).toEqual(XChainSeqNumCreate.binary)
})
test('can deserialize XChainSeqNumCreate', () => {
expect(decode(XChainSeqNumCreate.binary)).toEqual(XChainSeqNumCreate.tx)
})
test('can serialize XChainTransfer', () => {
expect(encode(XChainTransfer.tx)).toEqual(XChainTransfer.binary)
})
test('can deserialize XChainTransfer', () => {
expect(decode(XChainTransfer.binary)).toEqual(XChainTransfer.tx)
})
test('can serialize XChainClaim', () => {
expect(encode(XChainClaim.tx)).toEqual(XChainClaim.binary)
})
test('can deserialize XChainClaim', () => {
expect(decode(XChainClaim.binary)).toEqual(XChainClaim.tx)
})
}
function omitUndefinedTest() {
test('omits fields with undefined value', () => {
let encodedOmitted = encode(json_omitted)
@@ -282,7 +313,8 @@ describe('Binary Serialization', function () {
describe('SignerListSet', SignerListSetTest)
describe('Escrow', EscrowTest)
describe('PaymentChannel', PaymentChannelTest)
describe('NegativeUNLTest', NegativeUNLTest)
describe('NegativeUNLTest', negativeUNLTest)
describe('SidechainTest', sidechainTest)
describe('OmitUndefined', omitUndefinedTest)
describe('TicketTest', ticketTest)
describe('NFToken', nfTokenTest)

View File

@@ -0,0 +1,59 @@
{
"binary": "1200212280000000240000000168400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD0207446304402203CEE125C7EEA970DFC4DEFE89D79666C109EDD8B33FC006DBD8F2CCFC8CFAD4B02200E4210829E31079D623B8C5E6A634611688F50C9D41FF16341407B443B9605178114B5F762798A53D543A014CAF8B297CFF8F2F937E88314CA64525733C3BEED910CFE2AE280D3C078DABB4B0119C48CAD01682D7A86296EF14523074D4852C02EA90000000000000000000000000000000000000000CC86E58C9B58D4CF71CB8C1B41F21BB290CE13D40000000000000000000000000000000000000000400000003B9ACA000000000101E019712103ADB44CA8E56F78A0096825E5667C450ABD5C24C34E027BC1AAF7E5BD114CB5B5764730450221008CC9842A6855A37131FE7FB978675DCF329AC5CD7C881FAF6D13CDC23363059F02203A10475640C2C09541A55098109BB3326D3F49E2304710736A4E3C2773539B01E1E019712102A14E886B3C3579FBAE3139F29728B903E6F4295AEE92160C8480695524D66A1576473045022100B93B51FDBE42634828EEF7F2E5FA950557283648C5D39196ED1B08F8B394959102201F162F43B41D0D9316BF3B7AFA3BC9CA0EC1FCECF6EE8C94F58616BEA2D31F2CE1E019712102F7390DCF3352060847B81666EBAC79D52DEA2443BDF58439F75397C45334E2DC764630440220254038C5D6106246AEBB2A94E60CE79102321FDFD0468AA40C5E7F2DF1C205FE02204CEE5B3BFFDDCB67AE593348E8D8551E82770415BC9581EF0EF20DD000DD550CE1E019712102498BD8CD9CA6A4BA567A2ECFA163F118AFD30511CBBA71429C2EC2F74D76059276473045022100AA28592882A3B7C769B32564EDF9F816179D42D8C0E3988567F816E9A5453C6002202BA57DC085B9EB8427FBD58E05B3617183AC216EC7066F03FA4896D8B449490EE1E019712103219642288DEE8A3AA8FEA1F7DAE9ED4D9A9F0EADA1E2DE3DB56DD9598D9AD81776473045022100B9D649491723810B705282B66EAD6075235A1954BEEE054FF68066FADE11DA1C02205D977AE7C6706224A5A3AA75A53E13CA3DE9D67084405CD9BB474554EEE5C070E1",
"tx": {
"Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Destination" : "rKT9gDkaedAosiHyHZTjyZs2HvXpzuiGmC",
"Fee": "10",
"Flags" : 2147483648,
"Sequence": 1,
"TransactionType" : "XChainClaim",
"XChainClaimProof" :
{
"amount" : "1000000000",
"sidechain" :
{
"dst_chain_door" : "rKeSSvHvaMZJp9ykaxutVwkhZgWuWMLnQt",
"dst_chain_issue" : "XRP",
"src_chain_door" : "rJvExveLEL4jNDEeLKCVdxaSCN9cEBnEQC",
"src_chain_issue" : "XRP"
},
"signatures" :
[
{
"XChainProofSig": {
"Signature" : "30450221008CC9842A6855A37131FE7FB978675DCF329AC5CD7C881FAF6D13CDC23363059F02203A10475640C2C09541A55098109BB3326D3F49E2304710736A4E3C2773539B01",
"PublicKey" : "03ADB44CA8E56F78A0096825E5667C450ABD5C24C34E027BC1AAF7E5BD114CB5B5"
}
},
{
"XChainProofSig": {
"Signature" : "3045022100B93B51FDBE42634828EEF7F2E5FA950557283648C5D39196ED1B08F8B394959102201F162F43B41D0D9316BF3B7AFA3BC9CA0EC1FCECF6EE8C94F58616BEA2D31F2C",
"PublicKey" : "02A14E886B3C3579FBAE3139F29728B903E6F4295AEE92160C8480695524D66A15"
}
},
{
"XChainProofSig": {
"Signature" : "30440220254038C5D6106246AEBB2A94E60CE79102321FDFD0468AA40C5E7F2DF1C205FE02204CEE5B3BFFDDCB67AE593348E8D8551E82770415BC9581EF0EF20DD000DD550C",
"PublicKey" : "02F7390DCF3352060847B81666EBAC79D52DEA2443BDF58439F75397C45334E2DC"
}
},
{
"XChainProofSig": {
"Signature" : "3045022100AA28592882A3B7C769B32564EDF9F816179D42D8C0E3988567F816E9A5453C6002202BA57DC085B9EB8427FBD58E05B3617183AC216EC7066F03FA4896D8B449490E",
"PublicKey" : "02498BD8CD9CA6A4BA567A2ECFA163F118AFD30511CBBA71429C2EC2F74D760592"
}
},
{
"XChainProofSig": {
"Signature" : "3045022100B9D649491723810B705282B66EAD6075235A1954BEEE054FF68066FADE11DA1C02205D977AE7C6706224A5A3AA75A53E13CA3DE9D67084405CD9BB474554EEE5C070",
"PublicKey" : "03219642288DEE8A3AA8FEA1F7DAE9ED4D9A9F0EADA1E2DE3DB56DD9598D9AD817"
}
}
],
"was_src_chain_send" : true,
"xchain_seq" : 1
},
"SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
"TxnSignature": "304402203CEE125C7EEA970DFC4DEFE89D79666C109EDD8B33FC006DBD8F2CCFC8CFAD4B02200E4210829E31079D623B8C5E6A634611688F50C9D41FF16341407B443B960517"
}
}

View File

@@ -0,0 +1,54 @@
{
"binary": "12001E2280000000240000000220230000000468400000000000000C73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100B892F08A56487806BCC077CAA854341CEA6A8A2697D4FD068F4D4A391ADC16AE02203D0251CF521FC914CB3CC451A8553662CC9B4B5A6E0747F26C8B836DA2E8F82E8114B5F762798A53D543A014CAF8B297CFF8F2F937E8F4EB130001811474A41942D90FDD8E4E8BB25A7E91843CFEDB9A5DE1EB1300018114C287E75E44FEB7AF3537173BB3A866A652C91502E1EB1300018114F5B6BA5BA9F91592A4B607E0397E47A298B95EA2E1EB13000181145720A5ABFA7D844BD615F4E62FA7C963E85B0C7DE1EB1300018114B7521887260F712472A8E5775EE6234042641C0CE1F10118C48CAD01682D7A86296EF14523074D4852C02EA90000000000000000000000000000000000000000CC86E58C9B58D4CF71CB8C1B41F21BB290CE13D4000000000000000000000000555344000000000027B6C49755570AD538DDD42EE417A4708F17EF76",
"tx": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Fee": "12",
"Flags": 2147483648,
"Sequence": 2,
"Sidechain": {
"dst_chain_door": "rKeSSvHvaMZJp9ykaxutVwkhZgWuWMLnQt",
"dst_chain_issue": {
"currency": "USD",
"issuer": "rhczJR49YsdxwtYTPvxeSc1Jjr7R748cHv"
},
"src_chain_door": "rJvExveLEL4jNDEeLKCVdxaSCN9cEBnEQC",
"src_chain_issue": "XRP"
},
"SignerEntries": [
{
"SignerEntry": {
"Account": "rBdjyperRHKTzdxnZhyN94MpjN2aknRX8G",
"SignerWeight": 1
}
},
{
"SignerEntry": {
"Account": "rJj2ty2MDGu7dtm1bvZMA5KuhzreNL2HHo",
"SignerWeight": 1
}
},
{
"SignerEntry": {
"Account": "rPQDTwG7tWYNzqjytf8YCYX6hZemGG9TTh",
"SignerWeight": 1
}
},
{
"SignerEntry": {
"Account": "r3AguhaYj2enNDz37mzJNskxcQKb3sAYjE",
"SignerWeight": 1
}
},
{
"SignerEntry": {
"Account": "rH5KrD1ocKBWq3Mf7WGy8tTtEi84M1uwGm",
"SignerWeight": 1
}
}
],
"SignerQuorum": 4,
"SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
"TransactionType": "XChainDoorCreate",
"TxnSignature": "3045022100B892F08A56487806BCC077CAA854341CEA6A8A2697D4FD068F4D4A391ADC16AE02203D0251CF521FC914CB3CC451A8553662CC9B4B5A6E0747F26C8B836DA2E8F82E"
}
}

View File

@@ -0,0 +1,15 @@
{
"binary": "12001F22800000008114621D345F8F094A085132431C69C89EC05D212CC20118C48CAD01682D7A86296EF14523074D4852C02EA90000000000000000000000000000000000000000CC86E58C9B58D4CF71CB8C1B41F21BB290CE13D40000000000000000000000000000000000000000",
"tx": {
"Account" : "r9A8UyNpW3X46FUc6P7JZqgn6WgAPjBwPg",
"Flags" : 2147483648,
"Sidechain" :
{
"dst_chain_door" : "rKeSSvHvaMZJp9ykaxutVwkhZgWuWMLnQt",
"dst_chain_issue" : "XRP",
"src_chain_door" : "rJvExveLEL4jNDEeLKCVdxaSCN9cEBnEQC",
"src_chain_issue" : "XRP"
},
"TransactionType" : "XChainSeqNumCreate"
}
}

View File

@@ -0,0 +1,17 @@
{
"binary": "1200202280000000202F0000000161400000003B9ACA0081142F3CC37C1D5616B3BBF1AABC49F6BFF46A9200870118C48CAD01682D7A86296EF14523074D4852C02EA90000000000000000000000000000000000000000CC86E58C9B58D4CF71CB8C1B41F21BB290CE13D40000000000000000000000000000000000000000",
"tx": {
"Account" : "rnJmYAiqEVngtnb5ckRroXLtCbWC7CRUBx",
"Amount" : "1000000000",
"Flags" : 2147483648,
"Sidechain" :
{
"dst_chain_door" : "rKeSSvHvaMZJp9ykaxutVwkhZgWuWMLnQt",
"dst_chain_issue" : "XRP",
"src_chain_door" : "rJvExveLEL4jNDEeLKCVdxaSCN9cEBnEQC",
"src_chain_issue" : "XRP"
},
"TransactionType" : "XChainTransfer",
"XChainSequence" : 1
}
}

View File

@@ -68,6 +68,20 @@ export interface SignerEntry {
}
}
export interface Sidechain {
dst_chain_door: string
dst_chain_issue: Currency
src_chain_door: string
src_chain_issue: Currency
}
export interface XChainProofSig {
XChainProofSig: {
Signature: string
PublicKey: string
}
}
/**
* This information is added to Transactions in request responses, but is not part
* of the canonical Transaction information on ledger. These fields are denoted with

View File

@@ -1,78 +0,0 @@
import { BaseRequest, BaseResponse } from './baseMethod'
/**
* The `federator_info` command asks the federator for information
* about the door account and other bridge-related information. This
* method only exists on sidechain federators. Expects a response in
* the form of a {@link FederatorInfoResponse}.
*
* @category Requests
*/
export interface FederatorInfoRequest extends BaseRequest {
command: 'federator_info'
}
/**
* Response expected from a {@link FederatorInfoRequest}.
*
* @category Responses
*/
export interface FederatorInfoResponse extends BaseResponse {
result: {
info: {
mainchain: {
door_status: {
initialized: boolean
status: 'open' | 'opening' | 'closed' | 'closing'
}
last_transaction_sent_seq: number
listener_info: {
state: 'syncing' | 'normal'
}
pending_transactions: Array<{
amount: string
destination_account: string
signatures: Array<{
public_key: string
seq: number
}>
}>
sequence: number
tickets: {
initialized: boolean
tickets: Array<{
status: 'taken' | 'available'
ticket_seq: number
}>
}
}
public_key: string
sidechain: {
door_status: {
initialized: boolean
status: 'open' | 'opening' | 'closed' | 'closing'
}
last_transaction_sent_seq: number
listener_info: {
state: 'syncing' | 'normal'
}
pending_transactions: Array<{
amount: string
destination_account: string
signatures: Array<{
public_key: string
seq: number
}>
}>
sequence: number
tickets: {
initialized: boolean
tickets: Array<{
status: 'taken' | 'available'
ticket_seq: number
}>
}
}
}
}
}

View File

@@ -23,7 +23,6 @@ import {
DepositAuthorizedRequest,
DepositAuthorizedResponse,
} from './depositAuthorized'
import { FederatorInfoRequest, FederatorInfoResponse } from './federatorInfo'
import { FeeRequest, FeeResponse } from './fee'
import {
GatewayBalancesRequest,
@@ -121,8 +120,6 @@ type Request =
// NFT methods
| NFTBuyOffersRequest
| NFTSellOffersRequest
// sidechain methods
| FederatorInfoRequest
/**
* @category Responses
@@ -171,8 +168,6 @@ type Response =
// NFT methods
| NFTBuyOffersResponse
| NFTSellOffersResponse
// sidechain methods
| FederatorInfoResponse
export {
Request,
@@ -268,7 +263,4 @@ export {
NFTBuyOffersResponse,
NFTSellOffersRequest,
NFTSellOffersResponse,
// sidechain methods
FederatorInfoRequest,
FederatorInfoResponse,
}

View File

@@ -45,3 +45,7 @@ export { SetRegularKey } from './setRegularKey'
export { SignerListSet } from './signerListSet'
export { TicketCreate } from './ticketCreate'
export { TrustSetFlagsInterface, TrustSetFlags, TrustSet } from './trustSet'
export { XChainClaim } from './xChainClaim'
export { XChainDoorCreate } from './xChainDoorCreate'
export { XChainSeqNumCreate } from './xChainSeqNumCreate'
export { XChainTransfer } from './xChainTransfer'

View File

@@ -50,6 +50,13 @@ import { SetRegularKey, validateSetRegularKey } from './setRegularKey'
import { SignerListSet, validateSignerListSet } from './signerListSet'
import { TicketCreate, validateTicketCreate } from './ticketCreate'
import { TrustSet, validateTrustSet } from './trustSet'
import { XChainClaim, validateXChainClaim } from './xChainClaim'
import { XChainDoorCreate, validateXChainDoorCreate } from './xChainDoorCreate'
import {
XChainSeqNumCreate,
validateXChainSeqNumCreate,
} from './xChainSeqNumCreate'
import { XChainTransfer, validateXChainTransfer } from './xChainTransfer'
/**
* @category Transaction Models
@@ -79,6 +86,10 @@ export type Transaction =
| SignerListSet
| TicketCreate
| TrustSet
| XChainClaim
| XChainDoorCreate
| XChainSeqNumCreate
| XChainTransfer
/**
* @category Transaction Models
@@ -203,6 +214,22 @@ export function validate(transaction: Record<string, unknown>): void {
validateTrustSet(tx)
break
case 'XChainClaim':
validateXChainClaim(tx)
break
case 'XChainDoorCreate':
validateXChainDoorCreate(tx)
break
case 'XChainSeqNumCreate':
validateXChainSeqNumCreate(tx)
break
case 'XChainTransfer':
validateXChainTransfer(tx)
break
default:
throw new ValidationError(
`Invalid field TransactionType: ${tx.TransactionType}`,

View File

@@ -0,0 +1,48 @@
import { ValidationError } from '../../errors'
import { Sidechain, XChainProofSig } from '../common'
import { BaseTransaction, validateBaseTransaction } from './common'
/**
* A XChainClaim transaction assigns, changes, or removes the regular key
* pair associated with an account.
*
* @category Transaction Models
*/
export interface XChainClaim extends BaseTransaction {
TransactionType: 'XChainClaim'
XChainClaimProof: {
amount: string
sidechain: Sidechain
signatures: XChainProofSig[]
was_src_chain_send: boolean
xchain_seq: number
}
}
/**
* Verify the form and type of a XChainClaim at runtime.
*
* @param tx - A XChainClaim Transaction.
* @throws When the XChainClaim is malformed.
*/
export function validateXChainClaim(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Sidechain !== undefined && typeof tx.Sidechain !== 'object') {
throw new ValidationError('XChainClaim: Sidechain must be an object')
}
if (tx.XChainSequence === undefined) {
throw new ValidationError('EscrowCancel: missing XChainSequence')
}
if (typeof tx.XChainSequence !== 'number') {
throw new ValidationError('EscrowCancel: XChainSequence must be a number')
}
}

View File

@@ -0,0 +1,64 @@
import { ValidationError } from '../../errors'
import { Sidechain, SignerEntry } from '../common'
import { BaseTransaction, validateBaseTransaction } from './common'
/**
* A XChainDoorCreate transaction assigns, changes, or removes the regular key
* pair associated with an account.
*
* @category Transaction Models
*/
export interface XChainDoorCreate extends BaseTransaction {
TransactionType: 'XChainDoorCreate'
Sidechain: Sidechain
SignerEntries: SignerEntry[]
SignerQuorum: number
}
const MAX_SIGNERS = 8
/**
* Verify the form and type of a XChainDoorCreate at runtime.
*
* @param tx - A XChainDoorCreate Transaction.
* @throws When the XChainDoorCreate is malformed.
*/
export function validateXChainDoorCreate(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Sidechain !== undefined && typeof tx.Sidechain !== 'object') {
throw new ValidationError('XChainDoorCreate: Sidechain must be an object')
}
if (tx.SignerQuorum === undefined) {
throw new ValidationError('SignerListSet: missing field SignerQuorum')
}
if (typeof tx.SignerQuorum !== 'number') {
throw new ValidationError('SignerListSet: invalid SignerQuorum')
}
if (tx.SignerEntries === undefined) {
throw new ValidationError('SignerListSet: missing field SignerEntries')
}
if (!Array.isArray(tx.SignerEntries)) {
throw new ValidationError('SignerListSet: invalid SignerEntries')
}
if (tx.SignerEntries.length === 0) {
throw new ValidationError(
'SignerListSet: need atleast 1 member in SignerEntries',
)
}
if (tx.SignerEntries.length > MAX_SIGNERS) {
throw new ValidationError(
'SignerListSet: maximum of 8 members allowed in SignerEntries',
)
}
}

View File

@@ -0,0 +1,30 @@
import { ValidationError } from '../../errors'
import { Sidechain } from '../common'
import { BaseTransaction, validateBaseTransaction } from './common'
/**
* A XChainSeqNumCreate transaction assigns, changes, or removes the regular key
* pair associated with an account.
*
* @category Transaction Models
*/
export interface XChainSeqNumCreate extends BaseTransaction {
TransactionType: 'XChainSeqNumCreate'
Sidechain: Sidechain
}
/**
* Verify the form and type of a XChainSeqNumCreate at runtime.
*
* @param tx - A XChainSeqNumCreate Transaction.
* @throws When the XChainSeqNumCreate is malformed.
*/
export function validateXChainSeqNumCreate(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Sidechain !== undefined && typeof tx.Sidechain !== 'object') {
throw new ValidationError('XChainSeqNumCreate: Sidechain must be an object')
}
}

View File

@@ -0,0 +1,40 @@
import { ValidationError } from '../../errors'
import { Sidechain } from '../common'
import { BaseTransaction, validateBaseTransaction } from './common'
/**
* A XChainTransfer transaction assigns, changes, or removes the regular key
* pair associated with an account.
*
* @category Transaction Models
*/
export interface XChainTransfer extends BaseTransaction {
TransactionType: 'XChainTransfer'
Sidechain: Sidechain
XChainSequence: 1
}
/**
* Verify the form and type of a XChainTransfer at runtime.
*
* @param tx - A XChainTransfer Transaction.
* @throws When the XChainTransfer is malformed.
*/
export function validateXChainTransfer(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Sidechain !== undefined && typeof tx.Sidechain !== 'object') {
throw new ValidationError('XChainTransfer: Sidechain must be an object')
}
if (tx.XChainSequence === undefined) {
throw new ValidationError('EscrowCancel: missing XChainSequence')
}
if (typeof tx.XChainSequence !== 'number') {
throw new ValidationError('EscrowCancel: XChainSequence must be a number')
}
}

View File

@@ -1,40 +0,0 @@
import { XrplError } from '../errors'
import { Payment } from '../models'
import { Memo } from '../models/common'
import { convertStringToHex } from './stringConversion'
/**
* Creates a cross-chain payment transaction.
*
* @param payment - The initial payment transaction. If the transaction is
* signed, then it will need to be re-signed. There must be no more than 2
* memos, since one memo is used for the sidechain destination account. The
* destination must be the sidechain's door account.
* @param destAccount - the destination account on the sidechain.
* @returns A cross-chain payment transaction, where the mainchain door account
* is the `Destination` and the destination account on the sidechain is encoded
* in the memos.
* @throws XrplError - if there are more than 2 memos.
* @category Utilities
*/
export default function createCrossChainPayment(
payment: Payment,
destAccount: string,
): Payment {
const destAccountHex = convertStringToHex(destAccount)
const destAccountMemo: Memo = { Memo: { MemoData: destAccountHex } }
const memos = payment.Memos ?? []
if (memos.length > 2) {
throw new XrplError(
'Cannot have more than 2 memos in a cross-chain transaction.',
)
}
const newMemos = [destAccountMemo, ...memos]
const newPayment = { ...payment, Memos: newMemos }
delete newPayment.TxnSignature
return newPayment
}

View File

@@ -29,6 +29,12 @@ const ledgerSpaces = {
paychan: 'x',
check: 'C',
depositPreauth: 'p',
negativeUnl: 'N',
nftokenOffer: 'q',
nftokenBuyOffers: 'h',
nftokenSellOffers: 'i',
sidechain: 'H',
xchainSeq: 'Q',
}
export default ledgerSpaces

View File

@@ -21,7 +21,6 @@ import { Response } from '../models/methods'
import { PaymentChannelClaim } from '../models/transactions/paymentChannelClaim'
import { Transaction } from '../models/transactions/transaction'
import createCrossChainPayment from './createCrossChainPayment'
import { deriveKeypair, deriveAddress, deriveXAddress } from './derive'
import getBalanceChanges from './getBalanceChanges'
import {
@@ -216,6 +215,5 @@ export {
encodeForMultiSigning,
encodeForSigning,
encodeForSigningClaim,
createCrossChainPayment,
parseNFTokenID,
}

View File

@@ -1,127 +0,0 @@
import { assert } from 'chai'
import {
createCrossChainPayment,
convertStringToHex,
Payment,
} from 'xrpl-local'
describe('createCrossChainPayment', function () {
it('successful xchain payment creation', function () {
const payment: Payment = {
TransactionType: 'Payment',
Account: 'rRandom',
Destination: 'rRandom2',
Amount: '3489303',
}
const sidechainAccount = 'rSidechain'
const expectedPayment = {
...payment,
Memos: [
{
Memo: {
MemoData: convertStringToHex(sidechainAccount),
},
},
],
}
const resultPayment = createCrossChainPayment(payment, sidechainAccount)
assert.deepEqual(resultPayment, expectedPayment)
// ensure that the original object wasn't modified
assert.notDeepEqual(resultPayment, payment)
})
it('successful xchain payment creation with memo', function () {
const memo = {
Memo: {
MemoData: 'deadbeef',
},
}
const payment: Payment = {
TransactionType: 'Payment',
Account: 'rRandom',
Destination: 'rRandom2',
Amount: '3489303',
Memos: [memo],
}
const sidechainAccount = 'rSidechain'
const expectedPayment = {
...payment,
Memos: [
{
Memo: {
MemoData: convertStringToHex(sidechainAccount),
},
},
memo,
],
}
const resultPayment = createCrossChainPayment(payment, sidechainAccount)
assert.deepEqual(resultPayment, expectedPayment)
// ensure that the original object wasn't modified
assert.notDeepEqual(resultPayment, payment)
})
it('removes TxnSignature', function () {
const payment: Payment = {
TransactionType: 'Payment',
Account: 'rRandom',
Destination: 'rRandom2',
Amount: '3489303',
TxnSignature: 'asodfiuaosdfuaosd',
}
const sidechainAccount = 'rSidechain'
const expectedPayment = {
...payment,
Memos: [
{
Memo: {
MemoData: convertStringToHex(sidechainAccount),
},
},
],
}
delete expectedPayment.TxnSignature
const resultPayment = createCrossChainPayment(payment, sidechainAccount)
assert.deepEqual(resultPayment, expectedPayment)
// ensure that the original object wasn't modified
assert.notDeepEqual(resultPayment, payment)
})
it('fails with 3 memos', function () {
const payment: Payment = {
TransactionType: 'Payment',
Account: 'rRandom',
Destination: 'rRandom2',
Amount: '3489303',
Memos: [
{
Memo: {
MemoData: '2934723843ace',
},
},
{
Memo: {
MemoData: '2934723843ace',
},
},
{
Memo: {
MemoData: '2934723843ace',
},
},
],
}
assert.throws(() => {
createCrossChainPayment(payment, 'rSidechain')
}, /Cannot have more than 2 memos/u)
})
})