Compare commits

...

43 Commits

Author SHA1 Message Date
Denis Angell
d98d2e5fdb Publish
- @transia/ripple-binary-codec@1.4.6-alpha.10
 - @transia/xahau-models@2.7.3-alpha.29
 - @transia/xrpl@2.7.3-alpha.28
2024-06-19 10:45:51 +02:00
Denis Angell
da39e14d46 Update definitions.json 2024-06-19 10:44:29 +02:00
Denis Angell
ae43c75504 Update package.json 2024-06-19 10:43:19 +02:00
Denis Angell
4ae9839f68 update models package name 2024-05-31 10:33:05 +02:00
Denis Angell
f77d135bb8 add first ledger sequence 2024-05-31 10:32:56 +02:00
Denis Angell
720880a383 add xrpl-models package 2024-05-23 18:26:23 +02:00
Denis Angell
1dbf35dbcb Update remit.ts 2024-05-19 11:18:03 +02:00
Denis Angell
d6411baf10 Update claimReward.ts 2024-05-19 11:17:55 +02:00
Denis Angell
ab86bae582 add missing root flags 2024-05-19 11:17:51 +02:00
Denis Angell
cb61711298 Publish
- @transia/xrpl@2.7.3-alpha.27
2024-05-10 10:02:19 +02:00
Denis Angell
b449a62c5b Update remit.ts 2024-05-10 10:00:01 +02:00
Denis Angell
5183289719 add Incoming Remit flag 2024-04-23 18:20:50 +02:00
Denis Angell
8ae372bb08 Publish
- @transia/ripple-binary-codec@1.4.6-alpha.9
 - @transia/xrpl@2.7.3-alpha.26
2024-03-13 11:34:06 +01:00
Denis Angell
63238773ea add remit 2024-03-13 11:33:29 +01:00
Denis Angell
4aacb1160a Update definitions.json 2024-03-13 11:13:27 +01:00
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
Denis Angell
ac865e472e Publish
- @transia/ripple-binary-codec@1.4.6-alpha.5
 - @transia/xrpl@2.7.3-alpha.18
2023-10-31 14:22:32 +01:00
Denis Angell
60c97901d2 XRP -> XAH 2023-10-31 14:22:03 +01:00
Denis Angell
3b234ec8ec Publish
- @transia/xrpl@2.7.3-alpha.17
2023-10-18 08:39:25 +02:00
Denis Angell
c7a001e7e9 Update uriTokenMint.ts 2023-10-18 08:38:54 +02:00
Denis Angell
e24cbccc2b Publish
- @transia/ripple-binary-codec@1.4.6-alpha.4
 - @transia/xrpl@2.7.3-alpha.16
2023-10-05 19:11:40 +02:00
Denis Angell
53310f699a add unl report tests 2023-10-05 19:11:04 +02:00
Denis Angell
46de80131a update definitions 2023-10-05 19:10:57 +02:00
Denis Angell
7981faf92f Publish
- @transia/xrpl@2.7.3-alpha.15
2023-09-27 08:19:48 +02:00
Denis Angell
0cf024cab1 Update UNLReport.ts 2023-09-27 08:19:15 +02:00
50 changed files with 1113 additions and 146 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.3",
"version": "1.4.6-alpha.10",
"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,
@@ -46,6 +46,7 @@
"NFTokenPage": 80,
"NFTokenOffer": 55,
"URIToken": 85,
"UNLReport": 82,
"Any": -3,
"Child": -2,
"Nickname": 110,
@@ -140,40 +141,40 @@
[
"LedgerEntry",
{
"nth": 1,
"nth": 257,
"isVLEncoded": false,
"isSerialized": false,
"isSigningField": true,
"isSigningField": false,
"type": "LedgerEntry"
}
],
[
"Transaction",
{
"nth": 1,
"nth": 257,
"isVLEncoded": false,
"isSerialized": false,
"isSigningField": true,
"isSigningField": false,
"type": "Transaction"
}
],
[
"Validation",
{
"nth": 1,
"nth": 257,
"isVLEncoded": false,
"isSerialized": false,
"isSigningField": true,
"isSigningField": false,
"type": "Validation"
}
],
[
"Metadata",
{
"nth": 1,
"nth": 257,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"isSerialized": false,
"isSigningField": false,
"type": "Metadata"
}
],
@@ -1997,6 +1998,16 @@
"type": "AccountID"
}
],
[
"Inform",
{
"nth": 99,
"isVLEncoded": true,
"isSerialized": true,
"isSigningField": true,
"type": "AccountID"
}
],
[
"Indexes",
{
@@ -2047,6 +2058,16 @@
"type": "Vector256"
}
],
[
"URITokenIDs",
{
"nth": 99,
"isVLEncoded": true,
"isSerialized": true,
"isSigningField": true,
"type": "Vector256"
}
],
[
"Paths",
{
@@ -2307,6 +2328,26 @@
"type": "STObject"
}
],
[
"MintURIToken",
{
"nth": 92,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STObject"
}
],
[
"AmountEntry",
{
"nth": 91,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STObject"
}
],
[
"Signers",
{
@@ -2486,6 +2527,16 @@
"isSigningField": true,
"type": "STArray"
}
],
[
"Amounts",
{
"nth": 92,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STArray"
}
]
],
"TRANSACTION_RESULTS": {
@@ -2520,11 +2571,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,
@@ -2556,7 +2607,6 @@
"temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255,
"temXCHAIN_TOO_MANY_ATTESTATIONS": -254,
"temHOOK_DATA_TOO_LARGE": -253,
"temHOOK_REJECTED": -252,
"tefFAILURE": -199,
"tefALREADY": -198,
@@ -2581,6 +2631,7 @@
"tefNFTOKEN_IS_NOT_TRANSFERABLE": -179,
"tefPAST_IMPORT_SEQ": -178,
"tefPAST_IMPORT_VL_SEQ": -177,
"tefNONDIR_EMIT": -176,
"terRETRY": -99,
"terFUNDS_SPENT": -98,
@@ -2598,6 +2649,7 @@
"terNO_HOOK": -86,
"tesSUCCESS": 0,
"tesPARTIAL": 1,
"tecCLAIM": 100,
"tecPATH_PARTIAL": 101,
@@ -2609,7 +2661,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,
@@ -2671,6 +2723,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": {
@@ -2708,6 +2761,7 @@
"URITokenBuy": 47,
"URITokenCreateSellOffer": 48,
"URITokenCancelSellOffer": 49,
"Remit": 95,
"GenesisMint": 96,
"Import": 97,
"ClaimReward": 98,

View File

@@ -12,7 +12,7 @@ const STANDARD_FORMAT_HEX_REGEX = /^0{24}[\x00-\x7F]{6}0{10}$/
*/
function isoToBytes(iso: string): Buffer {
const bytes = Buffer.alloc(20)
if (iso !== 'XRP') {
if (iso !== 'XAH') {
const isoBytes = iso.split('').map((c) => c.charCodeAt(0))
bytes.set(isoBytes, 12)
}
@@ -28,7 +28,7 @@ function isIsoCode(iso: string): boolean {
function isoCodeFromHex(code: Buffer): string | null {
const iso = code.toString()
if (iso === 'XRP') {
if (iso === 'XAH') {
return null
}
if (isIsoCode(iso)) {
@@ -89,7 +89,7 @@ class Currency extends Hash160 {
const hex = this.bytes.toString('hex')
if (XRP_HEX_REGEX.test(hex)) {
this._iso = 'XRP'
this._iso = 'XAH'
} else if (STANDARD_FORMAT_HEX_REGEX.test(hex)) {
this._iso = isoCodeFromHex(this.bytes.slice(12, 15))
} else {

View File

@@ -369,7 +369,7 @@ function pathSetBinaryTests() {
currency: 'BTC',
issuer: 'r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn',
},
{ currency: 'XRP' },
{ currency: 'XAH' },
{
currency: 'USD',
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',

View File

@@ -105,6 +105,16 @@ let json_omitted = {
}
const NegativeUNL = require('./fixtures/negative-unl.json')
const UNLReport = {
tx: require('./fixtures/unl-report.json'),
binary: require('./fixtures/unl-report-binary.json'),
meta: require('./fixtures/unl-report-meta-binary.json'),
}
const Remit = {
tx: require('./fixtures/remit-tx.json'),
binary: require('./fixtures/remit-binary.json'),
}
function bytesListTest() {
const list = new BytesList()
@@ -229,6 +239,18 @@ function NegativeUNLTest() {
})
}
function UNLReportTest() {
test('can serialize UNLReport', () => {
expect(encode(UNLReport.tx)).toEqual(UNLReport.binary)
})
test('can serialize UNLReport metadata', () => {
expect(encode(UNLReport.tx.meta)).toEqual(UNLReport.meta)
})
test('can deserialize UNLReport metadata', () => {
expect(decode(UNLReport.meta)).toEqual(UNLReport.tx.meta)
})
}
function omitUndefinedTest() {
test('omits fields with undefined value', () => {
let encodedOmitted = encode(json_omitted)
@@ -274,6 +296,12 @@ function nfTokenTest() {
}
}
function RemitTest() {
test('can serialize Remit', () => {
expect(encode(Remit.tx)).toEqual(Remit.binary)
})
}
describe('Binary Serialization', function () {
describe('nestedObjectTests', nestedObjectTests)
describe('BytesList', bytesListTest)
@@ -286,4 +314,6 @@ describe('Binary Serialization', function () {
describe('OmitUndefined', omitUndefinedTest)
describe('TicketTest', ticketTest)
describe('NFToken', nfTokenTest)
describe('UNLReport', UNLReportTest)
describe('Remit', RemitTest)
})

View File

@@ -2904,7 +2904,7 @@
"type_id": 6,
"is_native": true,
"type": "Amount",
"error": "1000000000000 absolute XRP is bigger than max native value 100000000000.0",
"error": "1000000000000 absolute XAH is bigger than max native value 100000000000.0",
"is_negative": false
},
{
@@ -2912,7 +2912,7 @@
"type_id": 6,
"is_native": true,
"type": "Amount",
"error": "10000000000000000000 absolute XRP is bigger than max native value 100000000000.0",
"error": "10000000000000000000 absolute XAH is bigger than max native value 100000000000.0",
"is_negative": true
}
]

View File

@@ -0,0 +1 @@
"12005F22000000002403EDEB4A2E00000001201B03EE5D3150116F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B68400000000000000C732102F9E33F16DF9507705EC954E3F94EB5F10D1FC4A354606DBE6297DBB1096FE65474473045022100E3FAE0EDEC3D6A8FF6D81BC9CF8288A61B7EEDE8071E90FF9314CB4621058D10022043545CF631706D700CEE65A1DB83EFDD185413808292D9D90F14D87D3DC2D8CB701A04DEADBEEF81147990EC5D1D8DF69E070A968D4B186986FDF06ED0831449FF0C73CA6AF9733DA805F76CA2C37776B7C46B806314757C4A9ED08284D61F3D8807280795F858BAB61DE05C220000000150156F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B7504DEADBEEFE1F05CE05B6140000000000003E8E1E05B61D4838D7EA4C68000000000000000000000000000555344000000000006B80F0F1D98AEDA846ED981F741C398FB2C4FD1E1F100136320AED08CC1F50DD5F23A1948AF86153A3F3B7593E5EC77D65A02BB1B29E05AB6AE"

View File

@@ -0,0 +1,39 @@
{
"TransactionType": "Remit",
"Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
"Destination": "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy",
"DestinationTag": 1,
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 65953073,
"Sequence": 65923914,
"SigningPubKey": "02F9E33F16DF9507705EC954E3F94EB5F10D1FC4A354606DBE6297DBB1096FE654",
"TxnSignature": "3045022100E3FAE0EDEC3D6A8FF6D81BC9CF8288A61B7EEDE8071E90FF9314CB4621058D10022043545CF631706D700CEE65A1DB83EFDD185413808292D9D90F14D87D3DC2D8CB",
"Amounts": [
{
"AmountEntry": {
"Amount": "1000"
}
},
{
"AmountEntry": {
"Amount": {
"currency": "USD",
"issuer": "rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX",
"value": "1"
}
}
}
],
"MintURIToken": {
"URI": "DEADBEEF",
"Digest": "6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B",
"Flags": 1
},
"URITokenIDs": [
"AED08CC1F50DD5F23A1948AF86153A3F3B7593E5EC77D65A02BB1B29E05AB6AE"
],
"InvoiceID": "6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B",
"Blob": "DEADBEEF",
"Inform": "rB5Ux4Lv2nRx6eeoAAsZmtctnBQ2LiACnk"
}

View File

@@ -0,0 +1 @@
"120068240000000026006D2E00684000000000000000730081140000000000000000000000000000000000000000E05F7121ED93B2BE467CAD2F9F56FB3A82BDFF17F84B09E34232DDE8FAF2FC72382F142655E1"

View File

@@ -0,0 +1 @@
"201C00000000F8E51100525661E32E7A24A238F1C619D5F9DDCC41A94B33B66C0163F7EFCC8A19C9FD6F28DCE6F05FE05F7121ED3ABC6740983BFB13FFD9728EBCC365A2877877D368FC28990819522300C92A698114A53F8465478D79DC0C764F0FB2B67AE92465FD5EE1E05F7121ED49F82B2FFD537F224A1E0A10DEEFC3C25CE3882979E6B327C9F18603D21F0A2281142BF2A77E25382EC1DEF521D81D24BC0FDD35BC9BE1E05F7121ED79EB0F6A9F01A039235E536D19F812B55ACF540C9E22CF62C271E0D42BFF51748114C45147960400DB6B763110CBCE8D641E9365F24EE1E05F7121ED93B2BE467CAD2F9F56FB3A82BDFF17F84B09E34232DDE8FAF2FC72382F142655811408BCFB092DEE1BF0F2662AECE4DCC62C4AECCB8AE1E05F7121ED96F581FED430E8CBE1F08B37408857001D4118D49FBB594B0BE007C2DBFFD3678114A4EF485B50A7D91DF45450A680FAC31C53367B2DE1E05F7121EDCF31B8F683345E1C49B4A1D85BF2731E55E7D6781F3D4BF45EE7ADF2D2FB340281144D854497B48F1A41EF04E09DB656A11AAD01A703E1E05F7121EDDF197FC59A7FAA09EB1AD60A4638BA6201DD51497B5C08A1745115098E229E0E811446550CBD2B655081662AE3159B0488DE27EC0592E1F1E1E72200000000F05EE05E7121ED264807102805220DA0F312E71FC2C69E1552C9C5790F6C25E3729DEB573D5860811478339DD5880A994A5CB6E56CB7ED13FEEF201928E1F1F05FE05F7121ED93B2BE467CAD2F9F56FB3A82BDFF17F84B09E34232DDE8FAF2FC72382F142655811408BCFB092DEE1BF0F2662AECE4DCC62C4AECCB8AE1F1E1E1F1031000"

View File

@@ -0,0 +1,89 @@
{
"Account": "",
"ActiveValidator": {
"PublicKey": "ED93B2BE467CAD2F9F56FB3A82BDFF17F84B09E34232DDE8FAF2FC72382F142655"
},
"Fee": "0",
"LedgerSequence": 7155200,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "UNLReport",
"hash": "0878863F758F74A5CBD35691CDAB625A3BCD35B21B440E20545C4757DDB0CA43",
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"ActiveValidators": [
{
"ActiveValidator": {
"Account": "roUo3ygV92bdhfE1v9LGpPETXvJv2kQv5",
"PublicKey": "ED93B2BE467CAD2F9F56FB3A82BDFF17F84B09E34232DDE8FAF2FC72382F142655"
}
}
],
"Flags": 0,
"ImportVLKeys": [
{
"ImportVLKey": {
"Account": "rBxZvQBY551DJ21g9AC1Qc9ASQowqcskbF",
"PublicKey": "ED264807102805220DA0F312E71FC2C69E1552C9C5790F6C25E3729DEB573D5860"
}
}
]
},
"LedgerEntryType": "UNLReport",
"LedgerIndex": "61E32E7A24A238F1C619D5F9DDCC41A94B33B66C0163F7EFCC8A19C9FD6F28DC",
"PreviousFields": {
"ActiveValidators": [
{
"ActiveValidator": {
"Account": "rGhk2uLd8ShzX2Zrcgn8sQk1LWBG4jjEwf",
"PublicKey": "ED3ABC6740983BFB13FFD9728EBCC365A2877877D368FC28990819522300C92A69"
}
},
{
"ActiveValidator": {
"Account": "rnr4kwS1VkJhvjVRuq2fbWZtEdN2HbpVVu",
"PublicKey": "ED49F82B2FFD537F224A1E0A10DEEFC3C25CE3882979E6B327C9F18603D21F0A22"
}
},
{
"ActiveValidator": {
"Account": "rJupFrPPYgUNFBdoSqhMEJ22hiHKiZSHXQ",
"PublicKey": "ED79EB0F6A9F01A039235E536D19F812B55ACF540C9E22CF62C271E0D42BFF5174"
}
},
{
"ActiveValidator": {
"Account": "roUo3ygV92bdhfE1v9LGpPETXvJv2kQv5",
"PublicKey": "ED93B2BE467CAD2F9F56FB3A82BDFF17F84B09E34232DDE8FAF2FC72382F142655"
}
},
{
"ActiveValidator": {
"Account": "rGsa7f4arJ8JE9ok9LCht6jCu5xBKUKVMq",
"PublicKey": "ED96F581FED430E8CBE1F08B37408857001D4118D49FBB594B0BE007C2DBFFD367"
}
},
{
"ActiveValidator": {
"Account": "r3htgPchiR2r8kMGzPK3Wfv3WTrpaRKjtU",
"PublicKey": "EDCF31B8F683345E1C49B4A1D85BF2731E55E7D6781F3D4BF45EE7ADF2D2FB3402"
}
},
{
"ActiveValidator": {
"Account": "rfQtB8m51sdbWgcmddRX2mMjMpSxzX1AGr",
"PublicKey": "EDDF197FC59A7FAA09EB1AD60A4638BA6201DD51497B5C08A1745115098E229E0E"
}
}
]
}
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS"
}
}

View File

@@ -77,7 +77,7 @@ describe('Hash256', function () {
describe('Currency', function () {
test('Decoding allows dodgy XRP without throwing', function () {
const currencyCode = '0000000000000000000000005852500000000000'
const currencyCode = '0000000000000000000000005841480000000000'
expect(Currency.from(currencyCode).toJSON()).toBe(currencyCode)
})
test('Currency code with lowercase letters decodes to ISO code', () => {
@@ -113,7 +113,7 @@ describe('Currency', function () {
test('can be constructed from a Buffer', function () {
const xrp = new Currency(Buffer.alloc(20))
expect(xrp.iso()).toBe('XRP')
expect(xrp.iso()).toBe('XAH')
})
test('Can handle non-standard currency codes', () => {
const currency = '015841551A748AD2C1F76FF6ECB0CCCD00000000'

View File

@@ -58,7 +58,7 @@ describe('encoding and decoding tx_json', function () {
Sequence: 19,
LimitAmount: {
value: '200',
currency: '0000000000000000000000005852500000000000',
currency: '0000000000000000000000005841480000000000',
issuer: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
},
Fee: '10',

View File

@@ -0,0 +1,25 @@
{
"name": "@transia/xahau-models",
"version": "2.7.3-alpha.29",
"description": "",
"private": false,
"main": "dist/models/index.js",
"files": [
"dist/**/*"
],
"author": {
"name": "Denis Angell",
"url": "https://github.com/dangell7"
},
"license": "MIT",
"scripts": {},
"dependencies": {
"@types/node": "^20.4.2",
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
},
"engines": {
"node": ">=12.0.0",
"npm": ">=7.0.0"
}
}

View File

@@ -1698,7 +1698,7 @@ __OTHER CHANGES__
parseBinary: false
}
```
+ Added full currency name support, e.g. `Currency.from_json('XRP').to_human({full_name:'Ripples'})` will return `XRP - Ripples`
+ Added full currency name support, e.g. `Currency.from_json('XAH').to_human({full_name:'Ripples'})` will return `XAH - Ripples`
+ Improved interest bearing currency support, e.g. `Currency.from_human('USD - US Dollar (2.5%pa)')`
+ Improve test coverage
+ Added blob vault client. The vault client facilitates interaction with ripple's namespace and blob vault or 3rd party blob vaults using ripple's blob vault software (https://github.com/ripple/ripple-blobvault). A list of the available functions can be found at [docs/VAULTCLIENT.md](docs/VAULTCLIENT.md)

View File

@@ -1,6 +1,6 @@
{
"name": "@transia/xrpl",
"version": "2.7.3-alpha.14",
"version": "2.7.3-alpha.28",
"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.3",
"@transia/ripple-binary-codec": "^1.4.6-alpha.10",
"@transia/ripple-keypairs": "^1.1.8-alpha.0",
"bignumber.js": "^9.0.0",
"bip32": "^2.0.6",
@@ -54,6 +54,7 @@
"build": "run-s build:lib build:snippets build:web",
"build:snippets": "tsc --build ./snippets/tsconfig.json",
"build:lib": "tsc --build tsconfig.build.json",
"build:models": "tsc --build tsconfig.models.json",
"build:web": "webpack",
"build:browserTests": "webpack --config ./test/webpack.config.js",
"analyze": "run-s build:web --analyze",
@@ -80,5 +81,6 @@
"readmeFilename": "README.md",
"engines": {
"node": ">=10.13.0"
}
},
"gitHead": "3b234ec8ec1c677e0f3f534fd2985c985871c87e"
}

View File

@@ -18,7 +18,7 @@ async function createTxWithPaths(): Promise<void> {
source_account: wallet.classicAddress,
source_currencies: [
{
currency: 'XRP',
currency: 'XAH',
},
],
destination_account,

View File

@@ -485,10 +485,10 @@ class Wallet {
if (
txCurrency.length === standard_currency_code_len &&
txCurrency.toUpperCase() === 'XRP'
txCurrency.toUpperCase() === 'XAH'
) {
throw new XrplError(
`Trying to sign an issued currency with a similar standard code to XRP (received '${txCurrency}'). XRP is not an issued currency.`,
`Trying to sign an issued currency with a similar standard code to XAH (received '${txCurrency}'). XAH is not an issued currency.`,
)
}
@@ -575,7 +575,7 @@ function removeTrailingZeros(tx: Transaction): void {
/* eslint-disable @typescript-eslint/no-magic-numbers -- Magic numbers are from rippleds of currency code encoding */
function isoToHex(iso: string): string {
const bytes = Buffer.alloc(20)
if (iso !== 'XRP') {
if (iso !== 'XAH') {
const isoBytes = iso.split('').map((chr) => chr.charCodeAt(0))
bytes.set(isoBytes, 12)
}

View File

@@ -1,7 +1,7 @@
export type LedgerIndex = number | ('validated' | 'closed' | 'current')
interface XRP {
currency: 'XRP'
currency: 'XAH'
}
interface IssuedCurrency {
@@ -17,6 +17,8 @@ export interface IssuedCurrencyAmount extends IssuedCurrency {
export type Amount = IssuedCurrencyAmount | string
export type AmountEntry = Amount
export interface Signer {
Signer: {
Account: string
@@ -205,3 +207,21 @@ export interface EmitDetails {
EmitHookHash: string
EmitParentTxnID: string
}
/**
* The object that describes the uritoken in MintURIToken.
*/
export interface MintURIToken {
/**
*
*/
URI: string
/**
*
*/
Digest?: string
/**
* The flags that are set on the uritoken.
*/
Flags?: number
}

View File

@@ -72,6 +72,16 @@ export default interface AccountRoot extends BaseLedgerEntry {
* account to each other.
*/
TransferRate?: number
NFTokenMinter?: string
MintedNFTokens?: number
BurnedNFTokens?: number
HookStateCount?: number
HookNamespaces?: string[]
RewardLgrFirst?: number
RewardLgrLast?: number
RewardTime?: number
RewardAccumulator?: number
FirstNFTokenSequence?: number
ImportSequence?: string
GovernanceFlags?: string
GovernanceMarks?: string
@@ -136,6 +146,10 @@ export interface AccountRootFlagsInterface {
* Disallow incoming Trustlines from other accounts.
*/
lsfDisallowIncomingTrustline?: boolean
/**
* Disallow incoming Trustlines from other accounts.
*/
lsfDisallowIncomingRemit?: boolean
}
export enum AccountRootFlags {
@@ -192,4 +206,12 @@ export enum AccountRootFlags {
* Disallow incoming Trustlines from other accounts.
*/
lsfDisallowIncomingTrustline = 0x20000000,
/**
* The account has issued a URIToken.
*/
lsfURITokenIssuer = 0x40000000,
/**
* Disallow incoming Remits from other accounts.
*/
lsfDisallowIncomingRemit = 0x80000000,
}

View File

@@ -1,12 +1,16 @@
import BaseLedgerEntry from './BaseLedgerEntry'
interface ImportVLKey {
PublicKey: string
Account?: string
ImportVLKey: {
PublicKey: string
Account?: string
}
}
interface ActiveValidators {
PublicKey: string
Account?: string
interface ActiveValidator {
ActiveValidator: {
PublicKey: string
Account?: string
}
}
/**
@@ -24,7 +28,7 @@ export default interface UNLReport extends BaseLedgerEntry {
/**
*
*/
ActiveValidators?: ActiveValidators[]
ActiveValidators?: ActiveValidator[]
/**
* The identifying hash of the transaction that most recently modified this
* object.

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

@@ -57,6 +57,8 @@ export enum AccountSetAsfFlags {
asfDisallowIncomingPayChan = 14,
/** Disallow other accounts from creating incoming Trustlines */
asfDisallowIncomingTrustline = 15,
/** Disallow other accounts from sending incoming Remits */
asfDisallowIncomingRemit = 16,
}
/**

View File

@@ -1,6 +1,17 @@
import { ValidationError } from '../../errors'
import { BaseTransaction, validateBaseTransaction } from './common'
/**
* Transaction Flags for an ClaimReward Transaction.
*
* @category Transaction Flags
*/
export enum ClaimRewardFlags {
/**
* If set, indicates that the user would like to opt out of rewards.
*/
tfOptOut = 0x00000001,
}
/**
* ClaimReward is a transaction model that allows an account to claim rewards.
@@ -9,6 +20,7 @@ import { BaseTransaction, validateBaseTransaction } from './common'
*/
export interface ClaimReward extends BaseTransaction {
TransactionType: 'ClaimReward'
Flags?: number | ClaimRewardFlags
/** The unique address of the issuer where the reward.c hook is installed. */
Issuer?: string
}

View File

@@ -170,6 +170,10 @@ export interface BaseTransaction {
* The network id of the transaction.
*/
NetworkID?: number
/**
*
*/
FirstLedgerSequence?: number
/**
* The hook parameters of the transaction.
*/

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

@@ -44,8 +44,9 @@ export {
} from './paymentChannelClaim'
export { PaymentChannelCreate } from './paymentChannelCreate'
export { PaymentChannelFund } from './paymentChannelFund'
export { SetRegularKey } from './setRegularKey'
export { Remit } from './remit'
export { SetHookFlagsInterface, SetHookFlags, SetHook } from './setHook'
export { SetRegularKey } from './setRegularKey'
export { SignerListSet } from './signerListSet'
export { TicketCreate } from './ticketCreate'
export { TrustSetFlagsInterface, TrustSetFlags, TrustSet } from './trustSet'

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

@@ -0,0 +1,204 @@
import { ValidationError } from '../../errors'
import { AmountEntry, MintURIToken } from '../common'
import { isHex } from '../utils'
import { BaseTransaction, validateBaseTransaction } from './common'
const MAX_URI_LENGTH = 512
const DIGEST_LENGTH = 64
const MAX_ARRAY_LENGTH = 32
const MAX_BLOB_LENGTH = 1024
/**
* A Remit transaction represents a transfer of value from one account to
* another.
*
* @category Transaction Models
*/
export interface Remit extends BaseTransaction {
TransactionType: 'Remit'
/** The unique address of the account receiving the payment. */
Destination: string
/**
* Arbitrary tag that identifies the reason for the payment to the
* destination, or a hosted recipient to pay.
*/
DestinationTag?: number
/**
*
*/
Amounts?: AmountEntry[]
/**
*
*/
MintURIToken?: MintURIToken
/**
*
*/
URITokenIDs?: string[]
/**
* Arbitrary 256-bit hash representing a specific reason or identifier for
* this payment.
*/
InvoiceID?: string
/**
* Hex value representing a VL Blob.
*/
Blob?: string
/** The unique address of the account to inform */
Inform?: string
}
/**
* Verify the form and type of a Remit at runtime.
*
* @param tx - A Remit Transaction.
* @throws When the Remit is malformed.
*/
// eslint-disable-next-line complexity -- ignore
export function validateRemit(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Amounts !== undefined) {
checkAmounts(tx)
}
if (tx.URITokenIDs !== undefined) {
checkURITokenIDs(tx)
}
if (tx.Destination === tx.Account) {
throw new ValidationError(
'Remit: Destination must not be equal to the account',
)
}
if (tx.DestinationTag != null && typeof tx.DestinationTag !== 'number') {
throw new ValidationError('Remit: DestinationTag must be a number')
}
if (tx.Inform === tx.Account || tx.Inform === tx.Destination) {
throw new ValidationError(
'Remit: Inform must not be equal to the account or destination',
)
}
if (tx.MintURIToken !== undefined) {
checkMintURIToken(tx)
}
if (tx.Blob !== undefined && typeof tx.Blob !== 'string') {
throw new ValidationError('Remit: Blob must be a string')
}
if (tx.Blob !== undefined && typeof tx.Blob === 'string') {
if (!isHex(tx.Blob)) {
throw new ValidationError('Remit: Blob must be a hex string')
}
if (tx.Blob.length > MAX_BLOB_LENGTH) {
throw new ValidationError('Remit: max size Blob')
}
}
}
function checkAmounts(tx: Record<string, unknown>): void {
if (!Array.isArray(tx.Amounts)) {
throw new ValidationError('Remit: Amounts must be an array')
}
if (tx.Amounts.length < 1) {
throw new ValidationError('Remit: empty field Amounts')
}
if (tx.Amounts.length > MAX_ARRAY_LENGTH) {
throw new ValidationError('Remit: max field Amounts')
}
const seen = new Set<string>()
let seenXrp = false
for (const amount of tx.Amounts) {
if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- ignore
amount.AmountEntry === undefined ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- ignore
typeof amount.AmountEntry !== 'object'
) {
throw new ValidationError('Remit: invalid Amounts.AmountEntry')
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- ignore
if (typeof amount.AmountEntry.Amount === 'string') {
// eslint-disable-next-line max-depth -- ignore
if (seenXrp) {
throw new ValidationError(
'Remit: Duplicate Native amounts are not allowed',
)
}
seenXrp = true
} else {
// eslint-disable-next-line max-len -- ignore
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access -- ignore
const amountKey = `${amount.AmountEntry.Amount.currency}:${amount.AmountEntry.Amount.issuer}`
// eslint-disable-next-line max-depth -- ingore
if (seen.has(amountKey)) {
throw new ValidationError('Remit: Duplicate amounts are not allowed')
}
seen.add(amountKey)
}
}
}
function checkURITokenIDs(tx: Record<string, unknown>): void {
if (!Array.isArray(tx.URITokenIDs)) {
throw new ValidationError('Remit: invalid field URITokenIDs')
}
if (tx.URITokenIDs.length < 1) {
throw new ValidationError('Remit: empty field URITokenIDs')
}
if (tx.URITokenIDs.length > MAX_ARRAY_LENGTH) {
throw new ValidationError('Remit: max field URITokenIDs')
}
const seen = new Set<string>()
for (const token of tx.URITokenIDs) {
if (typeof token !== 'string' || !isHex(token)) {
throw new ValidationError('Remit: URITokenID must be a hex string')
}
if (token.length !== DIGEST_LENGTH) {
throw new ValidationError(
`Remit: URITokenID must be exactly ${DIGEST_LENGTH} characters`,
)
}
if (seen.has(token)) {
throw new ValidationError('Remit: Duplicate URITokens are not allowed')
}
seen.add(token)
}
}
// eslint-disable-next-line complexity -- ignore
function checkMintURIToken(tx: Record<string, unknown>): void {
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object'
}
if (!isRecord(tx.MintURIToken)) {
throw new ValidationError('Remit: invalid MintURIToken')
}
if (tx.MintURIToken.URI === undefined) {
throw new ValidationError('Remit: missing field MintURIToken.URI')
}
if (typeof tx.MintURIToken.URI !== 'string' || !isHex(tx.MintURIToken.URI)) {
throw new ValidationError('Remit: MintURIToken.URI must be a hex string')
}
if (tx.MintURIToken.URI.length > MAX_URI_LENGTH) {
throw new ValidationError(
`Remit: URI must be less than ${MAX_URI_LENGTH} characters`,
)
}
if (
tx.MintURIToken.Digest !== undefined &&
typeof tx.MintURIToken.Digest !== 'string'
) {
throw new ValidationError(`Remit: MintURIToken.Digest must be a string`)
}
if (
tx.MintURIToken.Digest !== undefined &&
!isHex(tx.MintURIToken.Digest) &&
tx.MintURIToken.Digest.length !== DIGEST_LENGTH
) {
throw new ValidationError(
`Remit: Digest must be exactly ${DIGEST_LENGTH} characters`,
)
}
}

View File

@@ -46,6 +46,7 @@ import {
PaymentChannelFund,
validatePaymentChannelFund,
} from './paymentChannelFund'
import { Remit, validateRemit } from './remit'
import { SetHook, validateSetHook } from './setHook'
import { SetRegularKey, validateSetRegularKey } from './setRegularKey'
import { SignerListSet, validateSignerListSet } from './signerListSet'
@@ -90,6 +91,7 @@ export type Transaction =
| PaymentChannelClaim
| PaymentChannelCreate
| PaymentChannelFund
| Remit
| SetHook
| SetRegularKey
| SignerListSet
@@ -220,14 +222,18 @@ export function validate(transaction: Record<string, unknown>): void {
validatePaymentChannelFund(tx)
break
case 'SetRegularKey':
validateSetRegularKey(tx)
case 'Remit':
validateRemit(tx)
break
case 'SetHook':
validateSetHook(tx)
break
case 'SetRegularKey':
validateSetRegularKey(tx)
break
case 'SignerListSet':
validateSignerListSet(tx)
break

View File

@@ -1,4 +1,5 @@
import { ValidationError } from '../../errors'
import { Amount } from '../common'
import { isHex } from '../utils'
import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common'
@@ -74,6 +75,23 @@ export interface URITokenMint extends BaseTransaction {
*/
URI: string
/**
* Indicates the amount expected or offered for the Token.
*
* The amount must be non-zero, except when this is a sell
* offer and the asset is XRP. This would indicate that the current
* owner of the token is giving it away free, either to anyone at all,
* or to the account identified by the Destination field.
*/
Amount?: Amount
/**
* If present, indicates that this offer may only be
* accepted by the specified account. Attempts by other
* accounts to accept this offer MUST fail.
*/
Destination?: string
Digest?: string
}

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

@@ -115,7 +115,7 @@ async function getBalances(
formatBalances(response.result.lines),
)
if (xrpBalance !== '') {
balances.push({ currency: 'XRP', value: xrpBalance })
balances.push({ currency: 'XAH', value: xrpBalance })
}
balances.push(...accountLinesBalance)
},

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)
}

View File

@@ -105,7 +105,7 @@ function getXRPQuantity(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- okay here
account: (node.FinalFields?.Account ?? node.NewFields?.Account) as string,
balance: {
currency: 'XRP',
currency: 'XAH',
value: dropsToXrp(value).toString(),
},
}

View File

@@ -4,6 +4,6 @@
"issuer": "rp8rJYTpodf8qbSCHVTNacf8nSW8mRakFw"
},
"takerGets": {
"currency": "XRP"
"currency": "XAH"
}
}

View File

@@ -1,7 +1,7 @@
[
{
"value": "922.913243",
"currency": "XRP"
"currency": "XAH"
},
{
"value": "0",

File diff suppressed because one or more lines are too long

View File

@@ -25,7 +25,7 @@ describe('book_offers', function () {
const bookOffer: BookOffersRequest = {
command: 'book_offers',
taker_gets: {
currency: 'XRP',
currency: 'XAH',
},
taker_pays: {
currency: 'USD',

View File

@@ -0,0 +1,369 @@
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import { validateRemit } from '../../src/models/transactions/remit'
/**
* Remit Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
// eslint-disable-next-line max-statements -- ignore
describe('Remit', function () {
let remit
beforeEach(function () {
remit = {
TransactionType: 'Remit',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
DestinationTag: 1,
Fee: '12',
Flags: 0,
LastLedgerSequence: 65953073,
Sequence: 65923914,
SigningPubKey:
'02F9E33F16DF9507705EC954E3F94EB5F10D1FC4A354606DBE6297DBB1096FE654',
TxnSignature:
'3045022100E3FAE0EDEC3D6A8FF6D81BC9CF8288A61B7EEDE8071E90FF9314CB4621058D10022043545CF631706D700CEE65A1DB83EFDD185413808292D9D90F14D87D3DC2D8CB',
Amounts: [
{ AmountEntry: { Amount: '1000' } },
{
AmountEntry: {
Amount: {
currency: 'USD',
issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
value: '1',
},
},
},
],
MintURIToken: {
URI: 'DEADBEEF',
Digest:
'6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B',
Flags: 1,
},
URITokenIDs: [
'AED08CC1F50DD5F23A1948AF86153A3F3B7593E5EC77D65A02BB1B29E05AB6AE',
],
InvoiceID:
'6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B',
Blob: 'DEADBEEF',
Inform: 'rB5Ux4Lv2nRx6eeoAAsZmtctnBQ2LiACnk',
} as any
})
it(`verifies valid Remit`, function () {
assert.doesNotThrow(() => validateRemit(remit))
assert.doesNotThrow(() => validate(remit))
})
it(`throws w/ Bad Amounts`, function () {
remit.Amounts = {}
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: Amounts must be an array',
)
})
it(`throws w/ Empty Amounts`, function () {
remit.Amounts = []
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: empty field Amounts',
)
})
it(`throws w/ Max Amounts`, function () {
remit.Amounts = [
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
]
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: max field Amounts',
)
})
it(`throws w/ Duplicate native amounts`, function () {
remit.Amounts = [
{ AmountEntry: { Amount: '1000' } },
{ AmountEntry: { Amount: '1000' } },
]
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: Duplicate Native amounts are not allowed',
)
})
it(`throws w/ Duplicate amounts`, function () {
remit.Amounts = [
{
AmountEntry: {
Amount: {
currency: 'USD',
issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
value: '1',
},
},
},
{
AmountEntry: {
Amount: {
currency: 'USD',
issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
value: '1',
},
},
},
]
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: Duplicate amounts are not allowed',
)
})
it(`throws w/ Bad URITokenIDs`, function () {
remit.URITokenIDs = {}
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: invalid field URITokenIDs',
)
})
it(`throws w/ Empty URITokenIDs`, function () {
remit.URITokenIDs = []
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: empty field URITokenIDs',
)
})
it(`throws w/ Empty URITokenIDs`, function () {
remit.URITokenIDs = [
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
'DEADBEEF',
]
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: max field URITokenIDs',
)
})
it(`throws w/ Invalid URITokenID`, function () {
remit.URITokenIDs = [1]
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: URITokenID must be a hex string',
)
})
it(`throws w/ Invalid URITokenID`, function () {
remit.URITokenIDs = ['ZZ11']
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: URITokenID must be a hex string',
)
})
it(`throws w/ Duplicate URITokenIDs`, function () {
remit.URITokenIDs = ['DEADBEEF']
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: URITokenID must be exactly 64 characters',
)
})
it(`throws w/ Duplicate URITokenIDs`, function () {
remit.URITokenIDs = [
'AED08CC1F50DD5F23A1948AF86153A3F3B7593E5EC77D65A02BB1B29E05AB6AE',
'AED08CC1F50DD5F23A1948AF86153A3F3B7593E5EC77D65A02BB1B29E05AB6AE',
]
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: Duplicate URITokens are not allowed',
)
})
// it(`throws w/ Bad MintURIToken`, function () {
// remit.MintURIToken = []
// assert.throws(
// () => validateRemit(remit),
// ValidationError,
// 'Remit: invalid MintURIToken',
// )
// })
it(`throws w/ Missing MintURIToken.URI`, function () {
remit.MintURIToken = {}
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: missing field MintURIToken.URI',
)
})
it(`throws w/ Bad MintURIToken.URI`, function () {
remit.MintURIToken = {
URI: 1,
}
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: MintURIToken.URI must be a hex string',
)
})
it(`throws w/ Bad MintURIToken.URI`, function () {
remit.MintURIToken = {
URI: 'ZZ11',
}
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: MintURIToken.URI must be a hex string',
)
})
it(`throws w/ Bad MintURIToken Less than 1`, function () {
remit.MintURIToken = {
URI: '',
}
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: MintURIToken.URI must be a hex string',
)
})
it(`throws w/ Bad MintURIToken.Digest`, function () {
remit.MintURIToken = {
URI: 'DEADBEEF',
Digest: 1,
}
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: MintURIToken.Digest must be a string',
)
})
it(`throws w/ Bad MintURIToken.Digest`, function () {
remit.MintURIToken = {
URI: 'DEADBEEF',
Digest: 'ZZ11',
}
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: Digest must be exactly 64 characters',
)
})
it(`throws w/ Bad Destination`, function () {
remit.Destination = 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo'
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: Destination must not be equal to the account',
)
})
it(`throws w/ Bad Destination Tag`, function () {
remit.DestinationTag = '1'
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: DestinationTag must be a number',
)
})
it(`throws w/ Bad Inform`, function () {
remit.Inform = 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo'
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: Inform must not be equal to the account or destination',
)
})
it(`throws w/ Bad Blob Type`, function () {
remit.Blob = 1
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: Blob must be a string',
)
})
it(`throws w/ Bad Blob Not Hex`, function () {
remit.Blob = 'ZZ11'
assert.throws(
() => validateRemit(remit),
ValidationError,
'Remit: Blob must be a hex string',
)
})
// it(`throws w/ Bad Blob Max Size`, function () {
// remit.Blob = ''
// assert.throws(
// () => validateRemit(remit),
// ValidationError,
// 'Remit: Blob must be a hex string',
// )
// })
})

View File

@@ -16,7 +16,7 @@ describe('TrustSet', function () {
TransactionType: 'TrustSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
LimitAmount: {
currency: 'XRP',
currency: 'XAH',
issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
value: '4329.23',
},

View File

@@ -118,7 +118,7 @@ describe('Models Utils', function () {
TransactionType: 'TrustSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
LimitAmount: {
currency: 'XRP',
currency: 'XAH',
issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
value: '4329.23',
},
@@ -166,7 +166,8 @@ describe('Models Utils', function () {
AccountRootFlags.lsfDisallowIncomingNFTokenOffer |
AccountRootFlags.lsfDisallowIncomingCheck |
AccountRootFlags.lsfDisallowIncomingPayChan |
AccountRootFlags.lsfDisallowIncomingTrustline
AccountRootFlags.lsfDisallowIncomingTrustline |
AccountRootFlags.lsfDisallowIncomingRemit
const parsed = parseAccountRootFlags(accountRootFlags)
@@ -183,7 +184,8 @@ describe('Models Utils', function () {
parsed.lsfDisallowIncomingNFTokenOffer &&
parsed.lsfDisallowIncomingCheck &&
parsed.lsfDisallowIncomingPayChan &&
parsed.lsfDisallowIncomingTrustline,
parsed.lsfDisallowIncomingTrustline &&
parsed.lsfDisallowIncomingRemit,
)
})
@@ -203,6 +205,7 @@ describe('Models Utils', function () {
assert.isUndefined(parsed.lsfDisallowIncomingCheck)
assert.isUndefined(parsed.lsfDisallowIncomingPayChan)
assert.isUndefined(parsed.lsfDisallowIncomingTrustline)
assert.isUndefined(parsed.lsfDisallowIncomingRemit)
})
})
})

View File

@@ -15,16 +15,16 @@ import trustlineSetLimit2 from '../fixtures/utils/trustlineSetLimit2.json'
import trustlineSetLimitZero from '../fixtures/utils/trustlineSetLimitZero.json'
describe('getBalanceChanges', function () {
it('XRP create account', function () {
it('XAH create account', function () {
const result = getBalanceChanges(paymentXrpCreateAccount.metadata)
const expected = [
{
account: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
balances: [{ currency: 'XRP', value: '100' }],
balances: [{ currency: 'XAH', value: '100' }],
},
{
account: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
balances: [{ currency: 'XRP', value: '-100.012' }],
balances: [{ currency: 'XAH', value: '-100.012' }],
},
]
assert.deepStrictEqual(result, expected)
@@ -43,7 +43,7 @@ describe('getBalanceChanges', function () {
},
{
value: '-0.012',
currency: 'XRP',
currency: 'XAH',
},
],
},
@@ -114,7 +114,7 @@ describe('getBalanceChanges', function () {
},
{
value: '-0.012',
currency: 'XRP',
currency: 'XAH',
},
],
},
@@ -136,7 +136,7 @@ describe('getBalanceChanges', function () {
},
{
value: '-0.012',
currency: 'XRP',
currency: 'XAH',
},
],
},
@@ -177,7 +177,7 @@ describe('getBalanceChanges', function () {
balances: [
{
value: '-0.012',
currency: 'XRP',
currency: 'XAH',
},
],
},
@@ -197,7 +197,7 @@ describe('getBalanceChanges', function () {
value: '10',
},
{
currency: 'XRP',
currency: 'XAH',
value: '-0.012',
},
],
@@ -224,7 +224,7 @@ describe('getBalanceChanges', function () {
balances: [
{
value: '-0.012',
currency: 'XRP',
currency: 'XAH',
},
],
},
@@ -239,7 +239,7 @@ describe('getBalanceChanges', function () {
account: 'rsApBGKJmMfExxZBrGnzxEXyq7TMhMRg4e',
balances: [
{
currency: 'XRP',
currency: 'XAH',
value: '-0.00001',
},
],
@@ -286,7 +286,7 @@ describe('getBalanceChanges', function () {
},
{
value: '-0.012',
currency: 'XRP',
currency: 'XAH',
},
],
},
@@ -316,7 +316,7 @@ describe('getBalanceChanges', function () {
value: '-100',
},
{
currency: 'XRP',
currency: 'XAH',
value: '-0.00001',
},
],
@@ -347,7 +347,7 @@ describe('getBalanceChanges', function () {
value: '-200',
},
{
currency: 'XRP',
currency: 'XAH',
value: '-0.00001',
},
],
@@ -383,7 +383,7 @@ describe('getBalanceChanges', function () {
value: '-100',
},
{
currency: 'XRP',
currency: 'XAH',
value: '-0.00001',
},
{

View File

@@ -757,13 +757,13 @@ describe('Wallet', function () {
it('sign throws when a payment contains an issued currency like XRP', async function () {
const payment: Payment = { ...issuedCurrencyPayment }
payment.Amount = {
currency: 'xrp',
currency: 'xah',
issuer: 'rnURbz5HLbvqEq69b1B4TX6cUTNMmcrBqi',
value: '123.40',
}
assert.throws(() => {
wallet.sign(payment)
}, /^Trying to sign an issued currency with a similar standard code to XRP \(received 'xrp'\)\. XRP is not an issued currency\./u)
}, /^Trying to sign an issued currency with a similar standard code to XAH \(received 'xah'\)\. XAH is not an issued currency\./u)
})
it('sign does NOT throw when a payment contains an issued currency like xrp in hex string format', async function () {

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "../xrpl-models/dist"
},
"include": ["./src/models/**/*.ts", "./src/errors.ts"],
}