Compare commits

..

19 Commits

Author SHA1 Message Date
Denis Angell
445d0c6633 Publish
- @transia/xrpl@2.7.3-alpha.25
2024-02-29 15:46:48 +01:00
Denis Angell
501d3158a0 fix account namespace error 2024-02-29 15:45:18 +01:00
Denis Angell
e27943aa3a add ID to cancel/create transactions 2024-02-29 15:43:33 +01:00
Denis Angell
c7aafb2595 add AccountNamespace and fix metadata 2024-02-29 15:42:17 +01:00
Denis Angell
036312d361 Publish
- @transia/ripple-binary-codec@1.4.6-alpha.8
 - @transia/xrpl@2.7.3-alpha.24
2024-01-27 18:19:08 +01:00
Denis Angell
3467680506 Update definitions.json 2024-01-27 18:18:29 +01:00
Denis Angell
f209126364 Publish
- @transia/ripple-binary-codec@1.4.6-alpha.7
 - @transia/xrpl@2.7.3-alpha.23
2024-01-27 17:56:23 +01:00
Denis Angell
33a8b3679a Update definitions.json 2024-01-27 17:55:14 +01:00
Denis Angell
572aaa7a40 Update settings.json 2024-01-05 02:52:16 +01:00
Denis Angell
b6c11d1df8 Publish
- @transia/xrpl@2.7.3-alpha.22
2023-12-17 08:40:26 +01:00
Denis Angell
526d1e7686 update autofill fee 2023-12-17 08:40:10 +01:00
Denis Angell
3c668f41b8 Publish
- @transia/xrpl@2.7.3-alpha.21
2023-12-15 12:09:52 +01:00
Denis Angell
aeb29a20a1 add fee estimation to autofill 2023-12-15 12:09:25 +01:00
Denis Angell
8c814482e5 Publish
- @transia/xrpl@2.7.3-alpha.20
2023-12-10 14:51:07 +01:00
Denis Angell
5b57ea8b77 add hook emissions to metadata 2023-12-10 14:49:51 +01:00
Denis Angell
7663e6049f always use open ledger fee 2023-12-10 14:48:53 +01:00
Denis Angell
420d6b2c35 Publish
- @transia/ripple-binary-codec@1.4.6-alpha.6
 - @transia/xrpl@2.7.3-alpha.19
2023-11-07 16:29:55 +01:00
Denis Angell
02c41d2eb1 Update definitions.json 2023-11-07 16:27:28 +01:00
Denis Angell
3f1a54f018 Update README.md 2023-10-31 15:12:07 +01:00
13 changed files with 139 additions and 86 deletions

View File

@@ -29,7 +29,7 @@
"enable": true
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,

View File

@@ -32,19 +32,19 @@ All of which works in Node.js (tested for v14+) & web browsers (tested for Chrom
In an existing project (with package.json), install xrpl.js with:
```
$ npm install --save xrpl
$ npm install --save @transia/xrpl
```
Or with `yarn`:
```
$ yarn add xrpl
$ yarn add @transia/xrpl
```
Example usage:
```js
const xrpl = require("xrpl");
const xrpl = require("@transia/xrpl");
async function main() {
const client = new xrpl.Client("wss://s.altnet.rippletest.net:51233");
await client.connect();

View File

@@ -1,6 +1,6 @@
{
"name": "@transia/ripple-binary-codec",
"version": "1.4.6-alpha.5",
"version": "1.4.6-alpha.8",
"description": "XRP Ledger binary codec",
"files": [
"dist/*",

View File

@@ -37,7 +37,7 @@
"LedgerHashes": 104,
"Amendments": 102,
"FeeSettings": 115,
"ImportVlseq": 73,
"ImportVLSequence": 73,
"Escrow": 117,
"PayChannel": 120,
"Check": 67,
@@ -2521,11 +2521,11 @@
"temBAD_PATH": -291,
"temBAD_PATH_LOOP": -290,
"temBAD_REGKEY": -289,
"temBAD_SEND_XRP_LIMIT": -288,
"temBAD_SEND_XRP_MAX": -287,
"temBAD_SEND_XRP_NO_DIRECT": -286,
"temBAD_SEND_XRP_PARTIAL": -285,
"temBAD_SEND_XRP_PATHS": -284,
"temBAD_SEND_NATIVE_LIMIT": -288,
"temBAD_SEND_NATIVE_MAX": -287,
"temBAD_SEND_NATIVE_NO_DIRECT": -286,
"temBAD_SEND_NATIVE_PARTIAL": -285,
"temBAD_SEND_NATIVE_PATHS": -284,
"temBAD_SEQUENCE": -283,
"temBAD_SIGNATURE": -282,
"temBAD_SRC_ACCOUNT": -281,
@@ -2557,7 +2557,6 @@
"temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255,
"temXCHAIN_TOO_MANY_ATTESTATIONS": -254,
"temHOOK_DATA_TOO_LARGE": -253,
"temHOOK_REJECTED": -252,
"tefFAILURE": -199,
"tefALREADY": -198,
@@ -2582,6 +2581,7 @@
"tefNFTOKEN_IS_NOT_TRANSFERABLE": -179,
"tefPAST_IMPORT_SEQ": -178,
"tefPAST_IMPORT_VL_SEQ": -177,
"tefNONDIR_EMIT": -176,
"terRETRY": -99,
"terFUNDS_SPENT": -98,
@@ -2610,7 +2610,7 @@
"tecINSUF_RESERVE_LINE": 122,
"tecINSUF_RESERVE_OFFER": 123,
"tecNO_DST": 124,
"tecNO_DST_INSUF_XRP": 125,
"tecNO_DST_INSUF_NATIVE": 125,
"tecNO_LINE_INSUF_RESERVE": 126,
"tecNO_LINE_REDUNDANT": 127,
"tecPATH_DRY": 128,
@@ -2672,6 +2672,7 @@
"tecXCHAIN_PAYMENT_FAILED": 184,
"tecXCHAIN_SELF_COMMIT": 185,
"tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 186,
"tecINSUF_RESERVE_SELLER": 187,
"tecLAST_POSSIBLE_ENTRY": 255
},
"TRANSACTION_TYPES": {

View File

@@ -1,6 +1,6 @@
{
"name": "@transia/xrpl",
"version": "2.7.3-alpha.18",
"version": "2.7.3-alpha.25",
"license": "ISC",
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
"files": [
@@ -23,7 +23,7 @@
},
"dependencies": {
"@transia/ripple-address-codec": "^4.2.8-alpha.0",
"@transia/ripple-binary-codec": "^1.4.6-alpha.5",
"@transia/ripple-binary-codec": "^1.4.6-alpha.8",
"@transia/ripple-keypairs": "^1.1.8-alpha.0",
"bignumber.js": "^9.0.0",
"bip32": "^2.0.6",

View File

@@ -0,0 +1,46 @@
import { HookState } from '../ledger'
import { BaseRequest, BaseResponse } from './baseMethod'
/**
* The `account_namespace` command retrieves the account namespace. All information retrieved is relative to a
* particular version of the ledger. Returns an {@link AccountNamespaceResponse}.
*
* @category Requests
*/
export interface AccountNamespaceRequest extends BaseRequest {
command: 'account_namespace'
/** A unique identifier for the account, most commonly the account's address. */
account: string
/** The hex namespace. */
namespace_id?: string
}
/**
* Response expected from an {@link AccountNamespaceRequest}.
*
* @category Responses
*/
export interface AccountNamespaceResponse extends BaseResponse {
result: {
/**
* The account requested.
*/
account: string
/**
* The namespace_id requested.
*/
namespace_id: string
/**
* A list of HookStates for the specified account namespace_id.
*/
namespace_entries: HookState[]
/**
* The ledger index of the current open ledger, which was used when
* retrieving this information.
*/
ledger_current_index: number
/** If true, this data comes from a validated ledger. */
validated: boolean
}
}

View File

@@ -8,6 +8,10 @@ import {
} from './accountCurrencies'
import { AccountInfoRequest, AccountInfoResponse } from './accountInfo'
import { AccountLinesRequest, AccountLinesResponse } from './accountLines'
import {
AccountNamespaceRequest,
AccountNamespaceResponse,
} from './accountNamespace'
import { AccountNFTsRequest, AccountNFTsResponse } from './accountNFTs'
import { AccountObjectsRequest, AccountObjectsResponse } from './accountObjects'
import {
@@ -83,6 +87,7 @@ type Request =
| AccountCurrenciesRequest
| AccountInfoRequest
| AccountLinesRequest
| AccountNamespaceRequest
| AccountNFTsRequest
| AccountObjectsRequest
| AccountOffersRequest
@@ -133,6 +138,7 @@ type Response =
| AccountCurrenciesResponse
| AccountInfoResponse
| AccountLinesResponse
| AccountNamespaceResponse
| AccountNFTsResponse
| AccountObjectsResponse
| AccountOffersResponse

View File

@@ -15,7 +15,12 @@ export interface EscrowCancel extends BaseTransaction {
* Transaction sequence (or Ticket number) of EscrowCreate transaction that.
* created the escrow to cancel.
*/
OfferSequence: number
OfferSequence?: number
/**
* The ID of the Escrow ledger object to cancel as a 64-character hexadecimal
* string.
*/
EscrowID?: string
}
/**
@@ -35,11 +40,17 @@ export function validateEscrowCancel(tx: Record<string, unknown>): void {
throw new ValidationError('EscrowCancel: Owner must be a string')
}
if (tx.OfferSequence === undefined) {
throw new ValidationError('EscrowCancel: missing OfferSequence')
if (tx.OfferSequence === undefined && tx.EscrowID === undefined) {
throw new ValidationError(
'EscrowCancel: must include OfferSequence or EscrowID',
)
}
if (typeof tx.OfferSequence !== 'number') {
throw new ValidationError('EscrowCancel: OfferSequence must be a number')
if (tx.OfferSequence !== undefined && typeof tx.OfferSequence !== 'number') {
throw new ValidationError('EscrowCancel: invalid OfferSequence')
}
if (tx.EscrowID !== undefined && typeof tx.EscrowID !== 'string') {
throw new ValidationError('EscrowCancel: invalid EscrowID')
}
}

View File

@@ -11,6 +11,16 @@ export interface HookExecution {
HookReturnCode: string
HookReturnString: string
HookStateChangeCount: number
Flags: number
}
}
export interface HookEmission {
HookEmission: {
EmittedTxnID: string
HookAccount: string
HookHash: string
EmitNonce: string
}
}
@@ -75,6 +85,7 @@ export function isDeletedNode(node: Node): node is DeletedNode {
export interface TransactionMetadata {
HookExecutions?: HookExecution[]
HookEmissions?: HookEmission[]
AffectedNodes: Node[]
DeliveredAmount?: Amount
// "unavailable" possible for transactions before 2014-01-20

View File

@@ -15,7 +15,12 @@ export interface OfferCancel extends BaseTransaction {
* created by that transaction. It is not considered an error if the offer.
* specified does not exist.
*/
OfferSequence: number
OfferSequence?: number
/**
* The ID of the Escrow ledger object to cancel as a 64-character hexadecimal
* string.
*/
OfferID?: string
}
/**
@@ -27,11 +32,17 @@ export interface OfferCancel extends BaseTransaction {
export function validateOfferCancel(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.OfferSequence === undefined) {
throw new ValidationError('OfferCancel: missing field OfferSequence')
if (tx.OfferSequence === undefined && tx.OfferID === undefined) {
throw new ValidationError(
'OfferCancel: must include OfferSequence or OfferID',
)
}
if (typeof tx.OfferSequence !== 'number') {
throw new ValidationError('OfferCancel: OfferSequence must be a number')
if (tx.OfferSequence !== undefined && typeof tx.OfferSequence !== 'number') {
throw new ValidationError('OfferCancel: invalid OfferSequence')
}
if (tx.OfferID !== undefined && typeof tx.OfferID !== 'string') {
throw new ValidationError('OfferCancel: invalid OfferID')
}
}

View File

@@ -103,6 +103,11 @@ export interface OfferCreate extends BaseTransaction {
Expiration?: number
/** An offer to delete first, specified in the same way as OfferCancel. */
OfferSequence?: number
/**
* The ID of the Offer ledger object to cancel as a 64-character hexadecimal
* string.
*/
OfferID?: string
/** The amount and type of currency being provided by the offer creator. */
TakerGets: Amount
/** The amount and type of currency being requested by the offer creator. */
@@ -141,4 +146,8 @@ export function validateOfferCreate(tx: Record<string, unknown>): void {
if (tx.OfferSequence !== undefined && typeof tx.OfferSequence !== 'number') {
throw new ValidationError('OfferCreate: invalid OfferSequence')
}
if (tx.OfferID !== undefined && typeof tx.OfferID !== 'string') {
throw new ValidationError('OfferCreate: invalid OfferID')
}
}

View File

@@ -2,16 +2,15 @@ import {
xAddressToClassicAddress,
isValidXAddress,
} from '@transia/ripple-address-codec'
import BigNumber from 'bignumber.js'
import { encode } from '@transia/ripple-binary-codec'
import type { Client } from '..'
import { ValidationError, XrplError } from '../errors'
import { AccountInfoRequest, AccountObjectsRequest } from '../models/methods'
import { Transaction } from '../models/transactions'
import { setTransactionFlagsToNumber } from '../models/utils/flags'
import { xrpToDrops } from '../utils'
import { getFeeXrp } from './getFeeXrp'
import { getFeeEstimateXrp } from './getFeeXrp'
// Expire unconfirmed transactions after 20 ledger versions, approximately 1 minute, by default
const LEDGER_OFFSET = 20
@@ -50,17 +49,18 @@ async function autofill<T extends Transaction>(
if (tx.Sequence == null) {
promises.push(setNextValidSequenceNumber(this, tx))
}
if (tx.Fee == null) {
promises.push(calculateFeePerTransactionType(this, tx, signersCount))
}
if (tx.LastLedgerSequence == null) {
promises.push(setLatestValidatedLedgerSequence(this, tx))
}
if (tx.TransactionType === 'AccountDelete') {
promises.push(checkAccountDeleteBlockers(this, tx))
}
await Promise.all(promises).then(() => tx)
return Promise.all(promises).then(() => tx)
if (tx.Fee == null) {
await calculateFeePerTransactionType(this, tx, signersCount)
}
return tx
}
function setValidAddresses(tx: Transaction): void {
@@ -146,64 +146,17 @@ async function setNextValidSequenceNumber(
tx.Sequence = data.result.account_data.Sequence
}
async function fetchAccountDeleteFee(client: Client): Promise<BigNumber> {
const response = await client.request({ command: 'server_state' })
const fee = response.result.state.validated_ledger?.reserve_inc
if (fee == null) {
return Promise.reject(new Error('Could not fetch Owner Reserve.'))
}
return new BigNumber(fee)
}
async function calculateFeePerTransactionType(
client: Client,
tx: Transaction,
signersCount = 0,
): Promise<void> {
// netFee is usually 0.00001 XRP (10 drops)
const netFeeXRP = await getFeeXrp(client)
const netFeeDrops = xrpToDrops(netFeeXRP)
let baseFee = new BigNumber(netFeeDrops)
// EscrowFinish Transaction with Fulfillment
if (tx.TransactionType === 'EscrowFinish' && tx.Fulfillment != null) {
const fulfillmentBytesSize: number = Math.ceil(tx.Fulfillment.length / 2)
// 10 drops × (33 + (Fulfillment size in bytes / 16))
const product = new BigNumber(
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- expected use of magic numbers
scaleValue(netFeeDrops, 33 + fulfillmentBytesSize / 16),
)
baseFee = product.dp(0, BigNumber.ROUND_CEIL)
}
// AccountDelete Transaction
if (tx.TransactionType === 'AccountDelete') {
baseFee = await fetchAccountDeleteFee(client)
}
/*
* Multi-signed Transaction
* 10 drops × (1 + Number of Signatures Provided)
*/
if (signersCount > 0) {
baseFee = BigNumber.sum(baseFee, scaleValue(netFeeDrops, 1 + signersCount))
}
const maxFeeDrops = xrpToDrops(client.maxFeeXRP)
const totalFee =
tx.TransactionType === 'AccountDelete'
? baseFee
: BigNumber.min(baseFee, maxFeeDrops)
// Round up baseFee and return it as a string
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-magic-numbers -- param reassign is safe, base 10 magic num
tx.Fee = totalFee.dp(0, BigNumber.ROUND_CEIL).toString(10)
}
function scaleValue(value, multiplier): string {
return new BigNumber(value).times(multiplier).toString()
const copyTx = { ...tx }
copyTx.SigningPubKey = ``
copyTx.Fee = `0`
const tx_blob = encode(copyTx)
// eslint-disable-next-line require-atomic-updates, no-param-reassign -- ignore
tx.Fee = await getFeeEstimateXrp(client, tx_blob, signersCount)
}
async function setLatestValidatedLedgerSequence(

View File

@@ -50,15 +50,20 @@ export async function getFeeXrp(
*
* @param client - The Client used to connect to the ledger.
* @param txBlob - The encoded transaction to estimate the fee for.
* @param signersCount - The number of multisigners.
* @returns The transaction fee.
*/
export async function getFeeEstimateXrp(
client: Client,
txBlob: string,
signersCount = 0,
): Promise<string> {
const response = await client.request({
command: 'fee',
tx_blob: txBlob,
})
return response.result.drops.base_fee
const openLedgerFee = response.result.drops.open_ledger_fee
const baseFee = new BigNumber(response.result.drops.base_fee)
const totalFee = BigNumber.sum(openLedgerFee, Number(baseFee) * signersCount)
return new BigNumber(totalFee.toFixed(NUM_DECIMAL_PLACES)).toString(BASE_10)
}