mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-05 13:25:48 +00:00
feat: add sidechain-specific RPCs/sugar (#1940)
* federator_info responses/requests * edit docstring * make strings narrower * export federator_info * implement createXchainPayment * add tests * fix dependency cycle * fix linter errors * rename xchain -> crosschain * rename more * edit HISTORY * fix docstrings * add another test
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xrpl-announce) for release announcements. We recommend that xrpl.js (ripple-lib) users stay up-to-date with the latest stable release.
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
* `federator_info` RPC support
|
||||
* Helper method for creating a cross-chain payment to/from a sidechain
|
||||
|
||||
### Fixed
|
||||
* Type of TrustSet transaction edited, specifically LimitAmount property type (fixed typescript issue)
|
||||
|
||||
78
packages/xrpl/src/models/methods/federatorInfo.ts
Normal file
78
packages/xrpl/src/models/methods/federatorInfo.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
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
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
DepositAuthorizedRequest,
|
||||
DepositAuthorizedResponse,
|
||||
} from './depositAuthorized'
|
||||
import { FederatorInfoRequest, FederatorInfoResponse } from './federatorInfo'
|
||||
import { FeeRequest, FeeResponse } from './fee'
|
||||
import {
|
||||
GatewayBalancesRequest,
|
||||
@@ -116,6 +117,8 @@ type Request =
|
||||
// NFT methods
|
||||
| NFTBuyOffersRequest
|
||||
| NFTSellOffersRequest
|
||||
// sidechain methods
|
||||
| FederatorInfoRequest
|
||||
|
||||
/**
|
||||
* @category Responses
|
||||
@@ -164,6 +167,8 @@ type Response =
|
||||
// NFT methods
|
||||
| NFTBuyOffersResponse
|
||||
| NFTSellOffersResponse
|
||||
// sidechain methods
|
||||
| FederatorInfoResponse
|
||||
|
||||
export {
|
||||
Request,
|
||||
@@ -258,4 +263,7 @@ export {
|
||||
NFTBuyOffersResponse,
|
||||
NFTSellOffersRequest,
|
||||
NFTSellOffersResponse,
|
||||
// sidechain methods
|
||||
FederatorInfoRequest,
|
||||
FederatorInfoResponse,
|
||||
}
|
||||
|
||||
40
packages/xrpl/src/utils/createCrossChainPayment.ts
Normal file
40
packages/xrpl/src/utils/createCrossChainPayment.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
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
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import { Response } from '../models/methods'
|
||||
import { PaymentChannelClaim } from '../models/transactions/paymentChannelClaim'
|
||||
import { Transaction } from '../models/transactions/transaction'
|
||||
|
||||
import createCrossChainPayment from './createCrossChainPayment'
|
||||
import { deriveKeypair, deriveXAddress } from './derive'
|
||||
import getBalanceChanges from './getBalanceChanges'
|
||||
import {
|
||||
@@ -46,6 +47,7 @@ import {
|
||||
qualityToDecimal,
|
||||
} from './quality'
|
||||
import signPaymentChannelClaim from './signPaymentChannelClaim'
|
||||
import { convertHexToString, convertStringToHex } from './stringConversion'
|
||||
import {
|
||||
rippleTimeToISOTime,
|
||||
isoTimeToRippleTime,
|
||||
@@ -134,32 +136,6 @@ function isValidAddress(address: string): boolean {
|
||||
return isValidXAddress(address) || isValidClassicAddress(address)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to its hex equivalent. Useful for Memos.
|
||||
*
|
||||
* @param string - The string to convert to Hex.
|
||||
* @returns The Hex equivalent of the string.
|
||||
* @category Utilities
|
||||
*/
|
||||
function convertStringToHex(string: string): string {
|
||||
return Buffer.from(string, 'utf8').toString('hex').toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts hex to its string equivalent. Useful to read the Domain field and some Memos.
|
||||
*
|
||||
* @param hex - The hex to convert to a string.
|
||||
* @param encoding - The encoding to use. Defaults to 'utf8' (UTF-8). 'ascii' is also allowed.
|
||||
* @returns The converted string.
|
||||
* @category Utilities
|
||||
*/
|
||||
function convertHexToString(
|
||||
hex: string,
|
||||
encoding: BufferEncoding = 'utf8',
|
||||
): string {
|
||||
return Buffer.from(hex, 'hex').toString(encoding)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are more pages of data.
|
||||
*
|
||||
@@ -238,4 +214,5 @@ export {
|
||||
encodeForMultiSigning,
|
||||
encodeForSigning,
|
||||
encodeForSigningClaim,
|
||||
createCrossChainPayment,
|
||||
}
|
||||
|
||||
27
packages/xrpl/src/utils/stringConversion.ts
Normal file
27
packages/xrpl/src/utils/stringConversion.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Converts a string to its hex equivalent. Useful for Memos.
|
||||
*
|
||||
* @param string - The string to convert to Hex.
|
||||
* @returns The Hex equivalent of the string.
|
||||
* @category Utilities
|
||||
*/
|
||||
function convertStringToHex(string: string): string {
|
||||
return Buffer.from(string, 'utf8').toString('hex').toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts hex to its string equivalent. Useful to read the Domain field and some Memos.
|
||||
*
|
||||
* @param hex - The hex to convert to a string.
|
||||
* @param encoding - The encoding to use. Defaults to 'utf8' (UTF-8). 'ascii' is also allowed.
|
||||
* @returns The converted string.
|
||||
* @category Utilities
|
||||
*/
|
||||
function convertHexToString(
|
||||
hex: string,
|
||||
encoding: BufferEncoding = 'utf8',
|
||||
): string {
|
||||
return Buffer.from(hex, 'hex').toString(encoding)
|
||||
}
|
||||
|
||||
export { convertHexToString, convertStringToHex }
|
||||
127
packages/xrpl/test/utils/createCrossChainPayment.ts
Normal file
127
packages/xrpl/test/utils/createCrossChainPayment.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user