diff --git a/.vscode/settings.json b/.vscode/settings.json index 24fb77e035..5dc2e1cf8e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,9 @@ { "editor.tabSize": 2, "cSpell.words": [ + "altnet", + "Autofills", + "Clawback", "hostid", "keypair", "keypairs", @@ -8,8 +11,11 @@ "multisigned", "multisigning", "preauthorization", + "rippletest", "secp256k1", - "Setf" + "Setf", + "Sidechains", + "xchain" ], "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode", diff --git a/packages/ripple-binary-codec/HISTORY.md b/packages/ripple-binary-codec/HISTORY.md index bde5802d9c..1c70831e4b 100644 --- a/packages/ripple-binary-codec/HISTORY.md +++ b/packages/ripple-binary-codec/HISTORY.md @@ -1,6 +1,8 @@ # ripple-binary-codec Release History ## Unreleased +### Added +- Support for the XChainBridge amendment. ## 1.9.0 (2023-08-24) diff --git a/packages/ripple-binary-codec/src/enums/definitions.json b/packages/ripple-binary-codec/src/enums/definitions.json index 1ab810e449..b6b48f440c 100644 --- a/packages/ripple-binary-codec/src/enums/definitions.json +++ b/packages/ripple-binary-codec/src/enums/definitions.json @@ -22,6 +22,7 @@ "UInt384": 22, "UInt512": 23, "Issue": 24, + "XChainBridge": 25, "Transaction": 10001, "LedgerEntry": 10002, "Validation": 10003, @@ -35,8 +36,11 @@ "Ticket": 84, "SignerList": 83, "Offer": 111, + "Bridge": 105, "LedgerHashes": 104, "Amendments": 102, + "XChainOwnedClaimID": 113, + "XChainOwnedCreateAccountClaimID": 116, "FeeSettings": 115, "Escrow": 117, "PayChannel": 120, @@ -233,6 +237,16 @@ "type": "UInt8" } ], + [ + "WasLockingChainSend", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], [ "LedgerEntryType", { @@ -983,6 +997,36 @@ "type": "UInt64" } ], + [ + "XChainClaimID", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "XChainAccountCreateCount", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "XChainAccountClaimCount", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], [ "EmailHash", { @@ -1583,6 +1627,26 @@ "type": "Amount" } ], + [ + "SignatureReward", + { + "nth": 29, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "MinAccountCreateAmount", + { + "nth": 30, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], [ "LPTokenBalance", { @@ -1933,6 +1997,66 @@ "type": "AccountID" } ], + [ + "OtherChainSource", + { + "nth": 18, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "OtherChainDestination", + { + "nth": 19, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "AttestationSignerAccount", + { + "nth": 20, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "AttestationRewardAccount", + { + "nth": 21, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "LockingChainDoor", + { + "nth": 22, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "IssuingChainDoor", + { + "nth": 23, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], [ "Indexes", { @@ -1983,6 +2107,26 @@ "type": "PathSet" } ], + [ + "LockingChainIssue", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Issue" + } + ], + [ + "IssuingChainIssue", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Issue" + } + ], [ "Asset", { @@ -2003,6 +2147,16 @@ "type": "Issue" } ], + [ + "XChainBridge", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "XChainBridge" + } + ], [ "TransactionMetaData", { @@ -2243,6 +2397,46 @@ "type": "STObject" } ], + [ + "XChainClaimProofSig", + { + "nth": 28, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "XChainCreateAccountProofSig", + { + "nth": 29, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "XChainClaimAttestationCollectionElement", + { + "nth": 30, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "XChainCreateAccountAttestationCollectionElement", + { + "nth": 31, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], [ "Signers", { @@ -2393,6 +2587,26 @@ "type": "STArray" } ], + [ + "XChainClaimAttestations", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "XChainCreateAccountAttestations", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], [ "AuthAccounts", { @@ -2461,6 +2675,12 @@ "temSEQ_AND_TICKET": -263, "temBAD_NFTOKEN_TRANSFER_FEE": -262, "temBAD_AMM_TOKENS": -261, + "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260, + "temXCHAIN_BAD_PROOF": -259, + "temXCHAIN_BRIDGE_BAD_ISSUES": -258, + "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257, + "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256, + "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255, "tefFAILURE": -199, "tefALREADY": -198, @@ -2497,6 +2717,7 @@ "terQUEUED": -89, "terPRE_TICKET": -88, "terNO_AMM": -87, + "terSUBMITTED": -86, "tesSUCCESS": 0, @@ -2538,6 +2759,7 @@ "tecKILLED": 150, "tecHAS_OBLIGATIONS": 151, "tecTOO_SOON": 152, + "tecHOOK_ERROR": 153, "tecMAX_SEQUENCE_REACHED": 154, "tecNO_SUITABLE_NFTOKEN_PAGE": 155, "tecNFTOKEN_BUY_SELL_MISMATCH": 156, @@ -2553,7 +2775,24 @@ "tecAMM_EMPTY": 166, "tecAMM_NOT_EMPTY": 167, "tecAMM_ACCOUNT": 168, - "tecINCOMPLETE": 169 + "tecINCOMPLETE": 169, + "tecXCHAIN_BAD_TRANSFER_ISSUE": 170, + "tecXCHAIN_NO_CLAIM_ID": 171, + "tecXCHAIN_BAD_CLAIM_ID": 172, + "tecXCHAIN_CLAIM_NO_QUORUM": 173, + "tecXCHAIN_PROOF_UNKNOWN_KEY": 174, + "tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 175, + "tecXCHAIN_WRONG_CHAIN": 176, + "tecXCHAIN_REWARD_MISMATCH": 177, + "tecXCHAIN_NO_SIGNERS_LIST": 178, + "tecXCHAIN_SENDING_ACCOUNT_MISMATCH": 179, + "tecXCHAIN_INSUFF_CREATE_AMOUNT": 180, + "tecXCHAIN_ACCOUNT_CREATE_PAST": 181, + "tecXCHAIN_ACCOUNT_CREATE_TOO_MANY": 182, + "tecXCHAIN_PAYMENT_FAILED": 183, + "tecXCHAIN_SELF_COMMIT": 184, + "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185, + "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186 }, "TRANSACTION_TYPES": { "Invalid": -1, @@ -2592,6 +2831,14 @@ "AMMVote": 38, "AMMBid": 39, "AMMDelete": 40, + "XChainCreateClaimID": 41, + "XChainCommit": 42, + "XChainClaim": 43, + "XChainAccountCreateCommit": 44, + "XChainAddClaimAttestation": 45, + "XChainAddAccountCreateAttestation": 46, + "XChainModifyBridge": 47, + "XChainCreateBridge": 48, "EnableAmendment": 100, "SetFee": 101, "UNLModify": 102 diff --git a/packages/ripple-binary-codec/src/types/index.ts b/packages/ripple-binary-codec/src/types/index.ts index 7b98d93510..2f2efef471 100644 --- a/packages/ripple-binary-codec/src/types/index.ts +++ b/packages/ripple-binary-codec/src/types/index.ts @@ -14,6 +14,7 @@ import { UInt32 } from './uint-32' import { UInt64 } from './uint-64' import { UInt8 } from './uint-8' import { Vector256 } from './vector-256' +import { XChainBridge } from './xchain-bridge' import { type SerializedType } from './serialized-type' import { DEFAULT_DEFINITIONS } from '../enums' @@ -34,6 +35,7 @@ const coreTypes: Record = { UInt32, UInt64, Vector256, + XChainBridge, } // Ensures that the DEFAULT_DEFINITIONS object connects these types to fields for serializing/deserializing diff --git a/packages/ripple-binary-codec/src/types/xchain-bridge.ts b/packages/ripple-binary-codec/src/types/xchain-bridge.ts new file mode 100644 index 0000000000..ec80027c43 --- /dev/null +++ b/packages/ripple-binary-codec/src/types/xchain-bridge.ts @@ -0,0 +1,128 @@ +import { BinaryParser } from '../serdes/binary-parser' + +import { AccountID } from './account-id' +import { JsonObject, SerializedType } from './serialized-type' +import { Buffer } from 'buffer/' +import { Issue, IssueObject } from './issue' + +/** + * Interface for JSON objects that represent cross-chain bridges + */ +interface XChainBridgeObject extends JsonObject { + LockingChainDoor: string + LockingChainIssue: IssueObject | string + IssuingChainDoor: string + IssuingChainIssue: IssueObject | string +} + +/** + * Type guard for XChainBridgeObject + */ +function isXChainBridgeObject(arg): arg is XChainBridgeObject { + const keys = Object.keys(arg).sort() + return ( + keys.length === 4 && + keys[0] === 'IssuingChainDoor' && + keys[1] === 'IssuingChainIssue' && + keys[2] === 'LockingChainDoor' && + keys[3] === 'LockingChainIssue' + ) +} + +/** + * Class for serializing/deserializing XChainBridges + */ +class XChainBridge extends SerializedType { + static readonly ZERO_XCHAIN_BRIDGE: XChainBridge = new XChainBridge( + Buffer.concat([ + Buffer.from([0x14]), + Buffer.alloc(40), + Buffer.from([0x14]), + Buffer.alloc(40), + ]), + ) + + static readonly TYPE_ORDER: { name: string; type: typeof SerializedType }[] = + [ + { name: 'LockingChainDoor', type: AccountID }, + { name: 'LockingChainIssue', type: Issue }, + { name: 'IssuingChainDoor', type: AccountID }, + { name: 'IssuingChainIssue', type: Issue }, + ] + + constructor(bytes: Buffer) { + super(bytes ?? XChainBridge.ZERO_XCHAIN_BRIDGE.bytes) + } + + /** + * Construct a cross-chain bridge from a JSON + * + * @param value XChainBridge or JSON to parse into an XChainBridge + * @returns An XChainBridge object + */ + static from( + value: T, + ): XChainBridge { + if (value instanceof XChainBridge) { + return value + } + + if (isXChainBridgeObject(value)) { + const bytes: Array = [] + this.TYPE_ORDER.forEach((item) => { + const { name, type } = item + if (type === AccountID) { + bytes.push(Buffer.from([0x14])) + } + const object = type.from(value[name]) + bytes.push(object.toBytes()) + }) + return new XChainBridge(Buffer.concat(bytes)) + } + + throw new Error('Invalid type to construct an XChainBridge') + } + + /** + * Read an XChainBridge from a BinaryParser + * + * @param parser BinaryParser to read the XChainBridge from + * @returns An XChainBridge object + */ + static fromParser(parser: BinaryParser): XChainBridge { + const bytes: Array = [] + + this.TYPE_ORDER.forEach((item) => { + const { type } = item + if (type === AccountID) { + parser.skip(1) + bytes.push(Buffer.from([0x14])) + } + const object = type.fromParser(parser) + bytes.push(object.toBytes()) + }) + + return new XChainBridge(Buffer.concat(bytes)) + } + + /** + * Get the JSON representation of this XChainBridge + * + * @returns the JSON interpretation of this.bytes + */ + toJSON(): XChainBridgeObject { + const parser = new BinaryParser(this.toString()) + const json = {} + XChainBridge.TYPE_ORDER.forEach((item) => { + const { name, type } = item + if (type === AccountID) { + parser.skip(1) + } + const object = type.fromParser(parser).toJSON() + json[name] = object + }) + return json as XChainBridgeObject + } +} + +export { XChainBridge, XChainBridgeObject } diff --git a/packages/ripple-binary-codec/test/fixtures/codec-fixtures.json b/packages/ripple-binary-codec/test/fixtures/codec-fixtures.json index f7995f77b1..9029084c5a 100644 --- a/packages/ripple-binary-codec/test/fixtures/codec-fixtures.json +++ b/packages/ripple-binary-codec/test/fixtures/codec-fixtures.json @@ -4435,227 +4435,414 @@ } } ], - "transactions": [{ - "binary": "1200002200000000240000003E6140000002540BE40068400000000000000A7321034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E74473045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F17962646398114550FC62003E785DC231A1058A05E56E3F09CF4E68314D4CC8AB5B21D86A82C3E9E8D0ECF2404B77FECBA", - "json": { - "Account": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", - "Destination": "rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj", - "TransactionType": "Payment", - "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639", - "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E", - "Amount": "10000000000", - "Fee": "10", - "Flags": 0, - "Sequence": 62 - } - }, - { - "binary": "12002315000A2200000000240015DAE161400000000000271068400000000000000A6BD5838D7EA4C680000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440B3154D968314FCEB58001E1B0C3A4CFB33DF9FF6C73207E5EAEB9BD07E2747672168E1A2786D950495C38BD8DEE3391BF45F3008DD36F4B12E7C07D82CA5250E8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMCreate", - "TxnSignature": "B3154D968314FCEB58001E1B0C3A4CFB33DF9FF6C73207E5EAEB9BD07E2747672168E1A2786D950495C38BD8DEE3391BF45F3008DD36F4B12E7C07D82CA5250E", - "Amount": "10000", - "Amount2": { - "currency": "ETH", - "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", - "value": "10000" - }, - "TradingFee": 10, - "Fee": "10", - "Flags": 0, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8" - } - }, - { - "binary": "1200242200010000240015DAE168400000000000000A6019D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874408073C588E7EF672DD171E414638D9AF8DBE9A1359E030DE3E1C9AA6A38A2CE9E138CB56482BB844F7228D48B1E4AD7D09BB7E9F639C115958EEEA374749CA00B8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMDeposit", - "TxnSignature": "8073C588E7EF672DD171E414638D9AF8DBE9A1359E030DE3E1C9AA6A38A2CE9E138CB56482BB844F7228D48B1E4AD7D09BB7E9F639C115958EEEA374749CA00B", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, - "Fee": "10", - "Flags": 65536, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8" - } - }, - { - "binary": "1200242200080000240015DAE16140000000000003E868400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744096CA066F42871C55088D2758D64148921B1ACAA5C6C648D0F7D675BBF47F87DF711F17C5BD172666D5AEC257520C587A849A6E063345609D91E121A78816EB048114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMDeposit", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "Amount": "1000", - "Fee": "10", - "Flags": 524288, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", - "TxnSignature": "96CA066F42871C55088D2758D64148921B1ACAA5C6C648D0F7D675BBF47F87DF711F17C5BD172666D5AEC257520C587A849A6E063345609D91E121A78816EB04" - } - }, - { - "binary": "1200242200100000240015DAE16140000000000003E868400000000000000A6BD511C37937E080000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440FC22B16A098C236ED7EDB3EBC983026DFD218A03C8BAA848F3E1D5389D5B8B00473C1178C5BA257BFA2DCD433C414690A430A5CFD71C1C0A7F7BF725EC1759018114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMDeposit", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "Amount": "1000", - "Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"}, - "Fee": "10", - "Flags": 1048576, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", - "TxnSignature": "FC22B16A098C236ED7EDB3EBC983026DFD218A03C8BAA848F3E1D5389D5B8B00473C1178C5BA257BFA2DCD433C414690A430A5CFD71C1C0A7F7BF725EC175901" - } - }, - { - "binary": "1200242200200000240015DAE16140000000000003E868400000000000000A6019D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440117CF90F9B113AD3BD638B6DB63562B37C287D5180F278B3CCF58FC14A5BAEE98307EA0F6DFE19E2FBA887C92955BA5D1A04F92ADAAEB309DE89C3610D074C098114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMDeposit", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "Amount": "1000", - "LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, - "Fee": "10", - "Flags": 2097152, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", - "TxnSignature": "117CF90F9B113AD3BD638B6DB63562B37C287D5180F278B3CCF58FC14A5BAEE98307EA0F6DFE19E2FBA887C92955BA5D1A04F92ADAAEB309DE89C3610D074C09" - } - }, - { - "binary": "1200242200400000240015DAE16140000000000003E868400000000000000A601B40000000000000197321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874405E51EBC6B52A7C3BA5D0AE2FC8F62E779B80182009B3108A87AB6D770D68F56053C193DB0640128E4765565970625B1E2878E116AC854E6DED412202CCDE0B0D8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMDeposit", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "Amount": "1000", - "EPrice": "25", - "Fee": "10", - "Flags": 4194304, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", - "TxnSignature": "5E51EBC6B52A7C3BA5D0AE2FC8F62E779B80182009B3108A87AB6D770D68F56053C193DB0640128E4765565970625B1E2878E116AC854E6DED412202CCDE0B0D" - } - }, - { - "binary": "1200252200010000240015DAE168400000000000000A601AD5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874409D4F41FC452526C0AD17191959D9B6D04A3C73B3A6C29E0F34C8459675A83A7A7D6E3021390EC8C9BE6C93E11C167E12016465E523F64F9EB3194B0A52E418028114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMWithdraw", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, - "Fee": "10", - "Flags": 65536, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", - "TxnSignature": "9D4F41FC452526C0AD17191959D9B6D04A3C73B3A6C29E0F34C8459675A83A7A7D6E3021390EC8C9BE6C93E11C167E12016465E523F64F9EB3194B0A52E41802" - } - }, - { - "binary": "1200252200080000240015DAE16140000000000003E868400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440E2C60D56C337D6D73E4B7D53579C93C666605494E82A89DD58CFDE79E2A4866BCF52370A2146877A2EF748E98168373710001133A51B645D89491849079035018114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMWithdraw", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "Amount": "1000", - "Fee": "10", - "Flags": 524288, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", - "TxnSignature": "E2C60D56C337D6D73E4B7D53579C93C666605494E82A89DD58CFDE79E2A4866BCF52370A2146877A2EF748E98168373710001133A51B645D8949184907903501" - } - }, - { - "binary": "1200252200100000240015DAE16140000000000003E868400000000000000A6BD511C37937E080000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440D2FCD7D03E53358BC6188BA88A7BA4FF2519B639C3B5C0EBCBDCB704426CA2837111430E92A6003D1CD0D81C63682C74839320539EC4F89B82AA5607714952028114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMWithdraw", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "Amount": "1000", - "Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"}, - "Fee": "10", - "Flags": 1048576, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", - "TxnSignature": "D2FCD7D03E53358BC6188BA88A7BA4FF2519B639C3B5C0EBCBDCB704426CA2837111430E92A6003D1CD0D81C63682C74839320539EC4F89B82AA560771495202" - } - }, - { - "binary": "1200252200200000240015DAE16140000000000003E868400000000000000A601AD5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744042DA5620E924E2D2059BBB4E0C4F03244140ACED93B543136FEEDF802165F814D09F45C7E2A4618468442516F4712A23B1D3332D5DBDBAE830337F39F259C90F8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMWithdraw", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "Amount": "1000", - "LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, - "Fee": "10", - "Flags": 2097152, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", - "TxnSignature": "42DA5620E924E2D2059BBB4E0C4F03244140ACED93B543136FEEDF802165F814D09F45C7E2A4618468442516F4712A23B1D3332D5DBDBAE830337F39F259C90F" - } - }, - { - "binary": "1200252200400000240015DAE16140000000000003E868400000000000000A601B40000000000000197321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744045BCEE5A12E5F5F1FB085A24F2F7FD962BBCB0D89A44A5319E3F7E3799E1870341880B6F684132971DDDF2E6B15356B3F407962D6D4E8DE10989F3B16E3CB90D8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMWithdraw", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "Amount": "1000", - "EPrice": "25", - "Fee": "10", - "Flags": 4194304, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", - "TxnSignature": "45BCEE5A12E5F5F1FB085A24F2F7FD962BBCB0D89A44A5319E3F7E3799E1870341880B6F684132971DDDF2E6B15356B3F407962D6D4E8DE10989F3B16E3CB90D" - } - }, - { - "binary": "1200272200000000240015DAE168400000000000000A6CD4C8E1BC9BF04000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0486DD4CC6F3B40B6C000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440F8EAAFB5EC1A69275167589969F0B9764BACE6BC8CC81482C2FC5ACCE691EDBD0D88D141137B1253BB1B9AC90A8A52CB37F5B6F7E1028B06DD06F91BE06F5A0F8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653AF019E01B81149A91957F8F16BC57F3F200CD8C98375BF1791586E1F10318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMBid", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "AuthAccounts": [{"AuthAccount": {"Account": "rEaHTti4HZsMBpxTAF4ncWxkcdqDh1h6P7"}}], - "BidMax": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "35"}, - "BidMin": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "25"}, - "Fee": "10", - "Flags": 0, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", - "TxnSignature": "F8EAAFB5EC1A69275167589969F0B9764BACE6BC8CC81482C2FC5ACCE691EDBD0D88D141137B1253BB1B9AC90A8A52CB37F5B6F7E1028B06DD06F91BE06F5A0F" - } - }, - { - "binary": "1200261500EA2200000000240015DAE168400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440BC2F6E76969E3747E9BDE183C97573B086212F09D5387460E6EE2F32953E85EAEB9618FBBEF077276E30E59D619FCF7C7BDCDDDD9EB94D7CE1DD5CE9246B21078114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", - "json": { - "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", - "TransactionType": "AMMVote", - "Asset": {"currency": "XRP"}, - "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, - "TradingFee": 234, - "Fee": "10", - "Flags": 0, - "Sequence": 1432289, - "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", - "TxnSignature": "BC2F6E76969E3747E9BDE183C97573B086212F09D5387460E6EE2F32953E85EAEB9618FBBEF077276E30E59D619FCF7C7BDCDDDD9EB94D7CE1DD5CE9246B2107" + "transactions": [ + { + "binary": "1200002200000000240000003E6140000002540BE40068400000000000000A7321034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E74473045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F17962646398114550FC62003E785DC231A1058A05E56E3F09CF4E68314D4CC8AB5B21D86A82C3E9E8D0ECF2404B77FECBA", + "json": { + "Account": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "Destination": "rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj", + "TransactionType": "Payment", + "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639", + "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E", + "Amount": "10000000000", + "Fee": "10", + "Flags": 0, + "Sequence": 62 + } + }, + { + "binary": "1200302200000000240000000168400000000000000A601D40000000000003E8601E400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220101BCA4B5B5A37C6F44480F9A34752C9AA8B2CDF5AD47E3CB424DEDC21C06DB702206EEB257E82A89B1F46A0A2C7F070B0BD181D980FF86FE4269E369F6FC7A270918114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Fee": "10", + "Flags": 0, + "MinAccountCreateAmount": "10000", + "Sequence": 1, + "SignatureReward": "1000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainCreateBridge", + "TxnSignature": "30440220101BCA4B5B5A37C6F44480F9A34752C9AA8B2CDF5AD47E3CB424DEDC21C06DB702206EEB257E82A89B1F46A0A2C7F070B0BD181D980FF86FE4269E369F6FC7A27091" + } + }, + { + "binary": "12002F2200000000240000000168400000000000000A601D40000000000003E8601E400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100D2CABC1B0E0635A8EE2E6554F6D474C49BC292C995C5C9F83179F4A60634B04C02205D1DB569D9593136F2FBEA7140010C8F46794D653AFDBEA8D30B8750BA4805E58114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Fee": "10", + "Flags": 0, + "MinAccountCreateAmount": "10000", + "Sequence": 1, + "SignatureReward": "1000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainModifyBridge", + "TxnSignature": "3045022100D2CABC1B0E0635A8EE2E6554F6D474C49BC292C995C5C9F83179F4A60634B04C02205D1DB569D9593136F2FBEA7140010C8F46794D653AFDBEA8D30B8750BA4805E5" + } + }, + { + "binary": "1200292280000000240000000168400000000000000A601D400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220247B20A1B9C48E21A374CB9B3E1FE2A7C528151868DF8D307E9FBE15237E531A02207C20C092DDCC525E583EF4AB7CB91E862A6DED19426997D3F0A2C84E2BE8C5DD8114B5F762798A53D543A014CAF8B297CFF8F2F937E8801214AF80285F637EE4AF3C20378F9DFB12511ACB8D27011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Fee": "10", + "Flags": 2147483648, + "OtherChainSource": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "Sequence": 1, + "SignatureReward": "10000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainCreateClaimID", + "TxnSignature": "30440220247B20A1B9C48E21A374CB9B3E1FE2A7C528151868DF8D307E9FBE15237E531A02207C20C092DDCC525E583EF4AB7CB91E862A6DED19426997D3F0A2C84E2BE8C5DD" + } + }, + { + "binary": "12002A228000000024000000013014000000000000000161400000000000271068400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074453043021F177323F0D93612C82A4393A99B23905A7E675753FD80C52997AFAB13F5F9D002203BFFAF457E90BDA65AABE8F8762BD96162FAD98A0C030CCD69B06EE9B12BBFFE8114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Amount": "10000", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Fee": "10", + "Flags": 2147483648, + "Sequence": 1, + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainCommit", + "TxnSignature": "3043021F177323F0D93612C82A4393A99B23905A7E675753FD80C52997AFAB13F5F9D002203BFFAF457E90BDA65AABE8F8762BD96162FAD98A0C030CCD69B06EE9B12BBFFE", + "XChainClaimID": "0000000000000001" + } + }, + { + "binary": "12002B228000000024000000013014000000000000000161400000000000271068400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220445F7469FDA401787D9EE8A9B6E24DFF81E94F4C09FD311D2C0A58FCC02C684A022029E2EF34A5EA35F50D5BB57AC6320AD3AE12C13C8D1379B255A486D72CED142E8114B5F762798A53D543A014CAF8B297CFF8F2F937E88314550FC62003E785DC231A1058A05E56E3F09CF4E6011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Amount": "10000", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Destination": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "Fee": "10", + "Flags": 2147483648, + "Sequence": 1, + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainClaim", + "TxnSignature": "30440220445F7469FDA401787D9EE8A9B6E24DFF81E94F4C09FD311D2C0A58FCC02C684A022029E2EF34A5EA35F50D5BB57AC6320AD3AE12C13C8D1379B255A486D72CED142E", + "XChainClaimID": "0000000000000001" + } + }, + { + "binary": "12002C228000000024000000016140000000000F424068400000000000000A601D400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD0207446304402202984DDE7F0B566F081F7953D7212BF031ACBF8860FE114102E9512C4C8768C77022070113F4630B1DC3045E4A98DDD648CEBC31B12774F7B44A1B8123CD2C9F5CF188114B5F762798A53D543A014CAF8B297CFF8F2F937E88314AF80285F637EE4AF3C20378F9DFB12511ACB8D27011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Amount": "1000000", + "Fee": "10", + "Flags": 2147483648, + "Destination": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "Sequence": 1, + "SignatureReward": "10000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainAccountCreateCommit", + "TxnSignature": "304402202984DDE7F0B566F081F7953D7212BF031ACBF8860FE114102E9512C4C8768C77022070113F4630B1DC3045E4A98DDD648CEBC31B12774F7B44A1B8123CD2C9F5CF18" + } + }, + { + "binary": "12002E2400000005201B0000000D30150000000000000006614000000000989680684000000000000014601D40000000000000647121ED1F4A024ACFEBDB6C7AA88DEDE3364E060487EA31B14CC9E0D610D152B31AADC27321EDF54108BA2E0A0D3DC2AE3897F8BE0EFE776AE8D0F9FB0D0B9D64233084A8DDD1744003E74AEF1F585F156786429D2FC87A89E5C6B5A56D68BFC9A6A329F3AC67CBF2B6958283C663A4522278CA162C69B23CF75149AF022B410EA0508C16F42058007640EEFCFA3DC2AB4AB7C4D2EBBC168CB621A11B82BABD86534DFC8EFA72439A49662D744073CD848E7A587A95B35162CDF9A69BB237E72C9537A987F5B8C394F30D81145E7A3E3D7200A794FA801C66CE3775B6416EE4128314C15F113E49BCC4B9FFF43CD0366C23ACD82F75638012143FD9ED9A79DEA67CB5D585111FEF0A29203FA0408014145E7A3E3D7200A794FA801C66CE3775B6416EE4128015145E7A3E3D7200A794FA801C66CE3775B6416EE4120010130101191486F0B1126CE1205E59FDFDD2661A9FB7505CA70F000000000000000000000000000000000000000014B5F762798A53D543A014CAF8B297CFF8F2F937E80000000000000000000000000000000000000000", + "json": { + "Account": "r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT", + "Amount": "10000000", + "AttestationRewardAccount": "r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT", + "AttestationSignerAccount": "r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT", + "Destination": "rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi", + "Fee": "20", + "LastLedgerSequence": 13, + "OtherChainSource": "raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym", + "PublicKey": "ED1F4A024ACFEBDB6C7AA88DEDE3364E060487EA31B14CC9E0D610D152B31AADC2", + "Sequence": 5, + "Signature": "EEFCFA3DC2AB4AB7C4D2EBBC168CB621A11B82BABD86534DFC8EFA72439A49662D744073CD848E7A587A95B35162CDF9A69BB237E72C9537A987F5B8C394F30D", + "SignatureReward": "100", + "SigningPubKey": "EDF54108BA2E0A0D3DC2AE3897F8BE0EFE776AE8D0F9FB0D0B9D64233084A8DDD1", + "TransactionType": "XChainAddAccountCreateAttestation", + "TxnSignature": "03E74AEF1F585F156786429D2FC87A89E5C6B5A56D68BFC9A6A329F3AC67CBF2B6958283C663A4522278CA162C69B23CF75149AF022B410EA0508C16F4205800", + "WasLockingChainSend": 1, + "XChainAccountCreateCount": "0000000000000006", + "XChainBridge": { + "IssuingChainDoor": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "IssuingChainIssue": { + "currency": "XRP" + }, + "LockingChainDoor": "rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg", + "LockingChainIssue": { + "currency": "XRP" + } + } + } + }, + { + "binary": "12002D2400000009201B00000013301400000000000000016140000000009896806840000000000000147121ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E11367321ED0406B134786FE0751717226657F7BF8AFE96442C05D28ACEC66FB64852BA604C7440D0423649E48A44F181262CF5FC08A68E7FA5CD9E55843E4F09014B76E602574741E8553383A4B43CABD194BB96713647FC0B885BE248E4FFA068FA3E6994CF0476407C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44528FE99AA50E71660A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C81141F30A4D728AB98B0950EC3B9815E6C8D43A7D5598314C15F113E49BCC4B9FFF43CD0366C23ACD82F75638012143FD9ED9A79DEA67CB5D585111FEF0A29203FA0408014141F30A4D728AB98B0950EC3B9815E6C8D43A7D5598015141F30A4D728AB98B0950EC3B9815E6C8D43A7D5590010130101191486F0B1126CE1205E59FDFDD2661A9FB7505CA70F000000000000000000000000000000000000000014B5F762798A53D543A014CAF8B297CFF8F2F937E80000000000000000000000000000000000000000", + "json": { + "Account": "rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3", + "Amount": "10000000", + "AttestationRewardAccount": "rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3", + "AttestationSignerAccount": "rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3", + "Destination": "rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi", + "Fee": "20", + "LastLedgerSequence": 19, + "OtherChainSource": "raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym", + "PublicKey": "ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E1136", + "Sequence": 9, + "Signature": "7C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44528FE99AA50E71660A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C", + "SigningPubKey": "ED0406B134786FE0751717226657F7BF8AFE96442C05D28ACEC66FB64852BA604C", + "TransactionType": "XChainAddClaimAttestation", + "TxnSignature": "D0423649E48A44F181262CF5FC08A68E7FA5CD9E55843E4F09014B76E602574741E8553383A4B43CABD194BB96713647FC0B885BE248E4FFA068FA3E6994CF04", + "WasLockingChainSend": 1, + "XChainBridge": { + "IssuingChainDoor": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "IssuingChainIssue": { + "currency": "XRP" + }, + "LockingChainDoor": "rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg", + "LockingChainIssue": { + "currency": "XRP" + } + }, + "XChainClaimID": "0000000000000001" + } + }, + { + "binary": "12002315000A2200000000240015DAE161400000000000271068400000000000000A6BD5838D7EA4C680000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440B3154D968314FCEB58001E1B0C3A4CFB33DF9FF6C73207E5EAEB9BD07E2747672168E1A2786D950495C38BD8DEE3391BF45F3008DD36F4B12E7C07D82CA5250E8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMCreate", + "TxnSignature": "B3154D968314FCEB58001E1B0C3A4CFB33DF9FF6C73207E5EAEB9BD07E2747672168E1A2786D950495C38BD8DEE3391BF45F3008DD36F4B12E7C07D82CA5250E", + "Amount": "10000", + "Amount2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", + "value": "10000" + }, + "TradingFee": 10, + "Fee": "10", + "Flags": 0, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8" + } + }, + { + "binary": "1200242200010000240015DAE168400000000000000A6019D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874408073C588E7EF672DD171E414638D9AF8DBE9A1359E030DE3E1C9AA6A38A2CE9E138CB56482BB844F7228D48B1E4AD7D09BB7E9F639C115958EEEA374749CA00B8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMDeposit", + "TxnSignature": "8073C588E7EF672DD171E414638D9AF8DBE9A1359E030DE3E1C9AA6A38A2CE9E138CB56482BB844F7228D48B1E4AD7D09BB7E9F639C115958EEEA374749CA00B", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "Fee": "10", + "Flags": 65536, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8" + } + }, + { + "binary": "1200242200080000240015DAE16140000000000003E868400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744096CA066F42871C55088D2758D64148921B1ACAA5C6C648D0F7D675BBF47F87DF711F17C5BD172666D5AEC257520C587A849A6E063345609D91E121A78816EB048114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMDeposit", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "Fee": "10", + "Flags": 524288, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", + "TxnSignature": "96CA066F42871C55088D2758D64148921B1ACAA5C6C648D0F7D675BBF47F87DF711F17C5BD172666D5AEC257520C587A849A6E063345609D91E121A78816EB04" + } + }, + { + "binary": "1200242200100000240015DAE16140000000000003E868400000000000000A6BD511C37937E080000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440FC22B16A098C236ED7EDB3EBC983026DFD218A03C8BAA848F3E1D5389D5B8B00473C1178C5BA257BFA2DCD433C414690A430A5CFD71C1C0A7F7BF725EC1759018114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMDeposit", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"}, + "Fee": "10", + "Flags": 1048576, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", + "TxnSignature": "FC22B16A098C236ED7EDB3EBC983026DFD218A03C8BAA848F3E1D5389D5B8B00473C1178C5BA257BFA2DCD433C414690A430A5CFD71C1C0A7F7BF725EC175901" + } + }, + { + "binary": "1200242200200000240015DAE16140000000000003E868400000000000000A6019D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440117CF90F9B113AD3BD638B6DB63562B37C287D5180F278B3CCF58FC14A5BAEE98307EA0F6DFE19E2FBA887C92955BA5D1A04F92ADAAEB309DE89C3610D074C098114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMDeposit", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "Fee": "10", + "Flags": 2097152, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", + "TxnSignature": "117CF90F9B113AD3BD638B6DB63562B37C287D5180F278B3CCF58FC14A5BAEE98307EA0F6DFE19E2FBA887C92955BA5D1A04F92ADAAEB309DE89C3610D074C09" + } + }, + { + "binary": "1200242200400000240015DAE16140000000000003E868400000000000000A601B40000000000000197321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874405E51EBC6B52A7C3BA5D0AE2FC8F62E779B80182009B3108A87AB6D770D68F56053C193DB0640128E4765565970625B1E2878E116AC854E6DED412202CCDE0B0D8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMDeposit", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "EPrice": "25", + "Fee": "10", + "Flags": 4194304, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", + "TxnSignature": "5E51EBC6B52A7C3BA5D0AE2FC8F62E779B80182009B3108A87AB6D770D68F56053C193DB0640128E4765565970625B1E2878E116AC854E6DED412202CCDE0B0D" + } + }, + { + "binary": "1200252200010000240015DAE168400000000000000A601AD5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B874409D4F41FC452526C0AD17191959D9B6D04A3C73B3A6C29E0F34C8459675A83A7A7D6E3021390EC8C9BE6C93E11C167E12016465E523F64F9EB3194B0A52E418028114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMWithdraw", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "Fee": "10", + "Flags": 65536, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", + "TxnSignature": "9D4F41FC452526C0AD17191959D9B6D04A3C73B3A6C29E0F34C8459675A83A7A7D6E3021390EC8C9BE6C93E11C167E12016465E523F64F9EB3194B0A52E41802" + } + }, + { + "binary": "1200252200080000240015DAE16140000000000003E868400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440E2C60D56C337D6D73E4B7D53579C93C666605494E82A89DD58CFDE79E2A4866BCF52370A2146877A2EF748E98168373710001133A51B645D89491849079035018114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMWithdraw", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "Fee": "10", + "Flags": 524288, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", + "TxnSignature": "E2C60D56C337D6D73E4B7D53579C93C666605494E82A89DD58CFDE79E2A4866BCF52370A2146877A2EF748E98168373710001133A51B645D8949184907903501" + } + }, + { + "binary": "1200252200100000240015DAE16140000000000003E868400000000000000A6BD511C37937E080000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440D2FCD7D03E53358BC6188BA88A7BA4FF2519B639C3B5C0EBCBDCB704426CA2837111430E92A6003D1CD0D81C63682C74839320539EC4F89B82AA5607714952028114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMWithdraw", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"}, + "Fee": "10", + "Flags": 1048576, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", + "TxnSignature": "D2FCD7D03E53358BC6188BA88A7BA4FF2519B639C3B5C0EBCBDCB704426CA2837111430E92A6003D1CD0D81C63682C74839320539EC4F89B82AA560771495202" + } + }, + { + "binary": "1200252200200000240015DAE16140000000000003E868400000000000000A601AD5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744042DA5620E924E2D2059BBB4E0C4F03244140ACED93B543136FEEDF802165F814D09F45C7E2A4618468442516F4712A23B1D3332D5DBDBAE830337F39F259C90F8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMWithdraw", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "Fee": "10", + "Flags": 2097152, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", + "TxnSignature": "42DA5620E924E2D2059BBB4E0C4F03244140ACED93B543136FEEDF802165F814D09F45C7E2A4618468442516F4712A23B1D3332D5DBDBAE830337F39F259C90F" + } + }, + { + "binary": "1200252200400000240015DAE16140000000000003E868400000000000000A601B40000000000000197321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8744045BCEE5A12E5F5F1FB085A24F2F7FD962BBCB0D89A44A5319E3F7E3799E1870341880B6F684132971DDDF2E6B15356B3F407962D6D4E8DE10989F3B16E3CB90D8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMWithdraw", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "EPrice": "25", + "Fee": "10", + "Flags": 4194304, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", + "TxnSignature": "45BCEE5A12E5F5F1FB085A24F2F7FD962BBCB0D89A44A5319E3F7E3799E1870341880B6F684132971DDDF2E6B15356B3F407962D6D4E8DE10989F3B16E3CB90D" + } + }, + { + "binary": "1200272200000000240015DAE168400000000000000A6CD4C8E1BC9BF04000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0486DD4CC6F3B40B6C000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440F8EAAFB5EC1A69275167589969F0B9764BACE6BC8CC81482C2FC5ACCE691EDBD0D88D141137B1253BB1B9AC90A8A52CB37F5B6F7E1028B06DD06F91BE06F5A0F8114F92F27CC5EE2F2760278FE096D0CBE32BDD3653AF019E01B81149A91957F8F16BC57F3F200CD8C98375BF1791586E1F10318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMBid", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "AuthAccounts": [{"AuthAccount": {"Account": "rEaHTti4HZsMBpxTAF4ncWxkcdqDh1h6P7"}}], + "BidMax": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "35"}, + "BidMin": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "25"}, + "Fee": "10", + "Flags": 0, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", + "TxnSignature": "F8EAAFB5EC1A69275167589969F0B9764BACE6BC8CC81482C2FC5ACCE691EDBD0D88D141137B1253BB1B9AC90A8A52CB37F5B6F7E1028B06DD06F91BE06F5A0F" + } + }, + { + "binary": "1200261500EA2200000000240015DAE168400000000000000A7321ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B87440BC2F6E76969E3747E9BDE183C97573B086212F09D5387460E6EE2F32953E85EAEB9618FBBEF077276E30E59D619FCF7C7BDCDDDD9EB94D7CE1DD5CE9246B21078114F92F27CC5EE2F2760278FE096D0CBE32BDD3653A0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rP5ZkB5RZQaECsSVR4DeSFK4fAw52BYtbw", + "TransactionType": "AMMVote", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "TradingFee": 234, + "Fee": "10", + "Flags": 0, + "Sequence": 1432289, + "SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8", + "TxnSignature": "BC2F6E76969E3747E9BDE183C97573B086212F09D5387460E6EE2F32953E85EAEB9618FBBEF077276E30E59D619FCF7C7BDCDDDD9EB94D7CE1DD5CE9246B2107" + } } - }], + ], "ledgerData": [{ "binary": "01E91435016340767BF1C4A3EACEB081770D8ADE216C85445DD6FB002C6B5A2930F2DECE006DA18150CB18F6DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F873B5C3E520634D343EF5D9D9A4246643D64DAD278BA95DC0EAC6EB5350CF970D521276CDE21276CE60A00", "json": { diff --git a/packages/xrpl/.eslintrc.js b/packages/xrpl/.eslintrc.js index 3d4b2d5e93..2321616dfa 100644 --- a/packages/xrpl/.eslintrc.js +++ b/packages/xrpl/.eslintrc.js @@ -84,7 +84,6 @@ module.exports = { 'max-statements': 'off', // Snippets have logs on console to better understand the working. 'no-console': 'off', - 'import/no-extraneous-dependencies': 'off', }, }, { @@ -147,5 +146,17 @@ module.exports = { 'import/no-unused-modules': 'off', }, }, + { + files: ['tools/*.ts', 'tools/*.js'], + rules: { + 'no-console': ['off'], + 'node/no-process-exit': ['off'], + '@typescript-eslint/no-magic-numbers': ['off'], + 'max-lines-per-function': ['off'], + 'max-statements': ['off'], + complexity: ['off'], + 'max-depth': ['warn', 3], + }, + }, ], } diff --git a/packages/xrpl/HISTORY.md b/packages/xrpl/HISTORY.md index 2a0a784f88..8cc5b5570e 100644 --- a/packages/xrpl/HISTORY.md +++ b/packages/xrpl/HISTORY.md @@ -3,9 +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 * Added `ports` field to `ServerInfoResponse` +* Support for the XChainBridge amendment. ### Fixed * Fix request model fields related to AMM diff --git a/packages/xrpl/snippets/src/bridgeTransfer.ts b/packages/xrpl/snippets/src/bridgeTransfer.ts new file mode 100644 index 0000000000..8043a14161 --- /dev/null +++ b/packages/xrpl/snippets/src/bridgeTransfer.ts @@ -0,0 +1,172 @@ +/* eslint-disable max-depth -- needed for attestation checking */ +/* eslint-disable @typescript-eslint/consistent-type-assertions -- needed here */ +/* eslint-disable no-await-in-loop -- needed here */ +import { + AccountObjectsRequest, + LedgerEntry, + Client, + XChainAccountCreateCommit, + XChainBridge, + XChainCommit, + XChainCreateClaimID, + xrpToDrops, + Wallet, + getXChainClaimID, +} from '../../src' + +async function sleep(sec: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, sec * 1000) + }) +} + +const lockingClient = new Client( + 'wss://sidechain-net1.devnet.rippletest.net:51233', +) +const issuingClient = new Client( + 'wss://sidechain-net2.devnet.rippletest.net:51233', +) +const MAX_LEDGERS_WAITED = 5 +const LEDGER_CLOSE_TIME = 4 + +void bridgeTransfer() + +async function bridgeTransfer(): Promise { + await lockingClient.connect() + await issuingClient.connect() + const lockingChainDoor = 'rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4' + + const accountObjectsRequest: AccountObjectsRequest = { + command: 'account_objects', + account: lockingChainDoor, + type: 'bridge', + } + const lockingAccountObjects = ( + await lockingClient.request(accountObjectsRequest) + ).result.account_objects + // There will only be one here - a door account can only have one bridge per currency + const bridgeData = lockingAccountObjects.filter( + (obj) => + obj.LedgerEntryType === 'Bridge' && + obj.XChainBridge.LockingChainIssue.currency === 'XRP', + )[0] as LedgerEntry.Bridge + const bridge: XChainBridge = bridgeData.XChainBridge + console.log(bridge) + + console.log('Creating wallet on the locking chain via the faucet...') + const { wallet: wallet1 } = await lockingClient.fundWallet() + console.log(wallet1) + const wallet2 = Wallet.generate() + + console.log( + `Creating ${wallet2.classicAddress} on the issuing chain via the bridge...`, + ) + + const fundTx: XChainAccountCreateCommit = { + TransactionType: 'XChainAccountCreateCommit', + Account: wallet1.classicAddress, + XChainBridge: bridge, + SignatureReward: bridgeData.SignatureReward, + Destination: wallet2.classicAddress, + Amount: ( + parseInt(bridgeData.MinAccountCreateAmount as string, 10) * 2 + ).toString(), + } + const fundResponse = await lockingClient.submitAndWait(fundTx, { + wallet: wallet1, + }) + console.log(fundResponse) + + console.log( + 'Waiting for the attestation to go through... (usually 8-12 seconds)', + ) + let ledgersWaited = 0 + let initialBalance = '0' + while (ledgersWaited < MAX_LEDGERS_WAITED) { + await sleep(LEDGER_CLOSE_TIME) + try { + initialBalance = await issuingClient.getXrpBalance(wallet2.classicAddress) + console.log( + `Wallet ${wallet2.classicAddress} has been funded with a balance of ${initialBalance} XRP`, + ) + break + } catch (_error) { + ledgersWaited += 1 + if (ledgersWaited === MAX_LEDGERS_WAITED) { + // This error should never be hit if the bridge is running + throw Error('Destination account creation via the bridge failed.') + } + } + } + + console.log( + `Transferring funds from ${wallet1.classicAddress} on the locking chain to ` + + `${wallet2.classicAddress} on the issuing_chain...`, + ) + + // Fetch the claim ID for the transfer + console.log('Step 1: Fetching the claim ID for the transfer...') + const claimIdTx: XChainCreateClaimID = { + TransactionType: 'XChainCreateClaimID', + Account: wallet2.classicAddress, + XChainBridge: bridge, + SignatureReward: bridgeData.SignatureReward, + OtherChainSource: wallet1.classicAddress, + } + const claimIdResult = await issuingClient.submitAndWait(claimIdTx, { + wallet: wallet2, + }) + console.log(claimIdResult) + + // Extract new claim ID from metadata + const xchainClaimId = getXChainClaimID(claimIdResult.result.meta) + if (xchainClaimId == null) { + // This shouldn't trigger assuming the transaction succeeded + throw Error('Could not extract XChainClaimID') + } + + console.log(`Claim ID for the transfer: ${xchainClaimId}`) + + console.log( + 'Step 2: Locking the funds on the locking chain with an XChainCommit transaction...', + ) + const commitTx: XChainCommit = { + TransactionType: 'XChainCommit', + Account: wallet1.classicAddress, + Amount: xrpToDrops(1), + XChainBridge: bridge, + XChainClaimID: xchainClaimId, + OtherChainDestination: wallet2.classicAddress, + } + const commitResult = await lockingClient.submitAndWait(commitTx, { + wallet: wallet1, + }) + console.log(commitResult) + + console.log( + 'Waiting for the attestation to go through... (usually 8-12 seconds)', + ) + ledgersWaited = 0 + while (ledgersWaited < MAX_LEDGERS_WAITED) { + await sleep(LEDGER_CLOSE_TIME) + const currentBalance = await issuingClient.getXrpBalance( + wallet2.classicAddress, + ) + console.log(initialBalance, currentBalance) + if (parseFloat(currentBalance) > parseFloat(initialBalance)) { + console.log('Transfer is complete') + console.log( + `New balance of ${wallet2.classicAddress} is ${currentBalance} XRP`, + ) + break + } + + ledgersWaited += 1 + if (ledgersWaited === MAX_LEDGERS_WAITED) { + throw Error('Bridge transfer failed.') + } + } + + await lockingClient.disconnect() + await issuingClient.disconnect() +} diff --git a/packages/xrpl/src/models/common/index.ts b/packages/xrpl/src/models/common/index.ts index e9cbdac36e..a695bd8e13 100644 --- a/packages/xrpl/src/models/common/index.ts +++ b/packages/xrpl/src/models/common/index.ts @@ -2,7 +2,7 @@ export type LedgerIndex = number | ('validated' | 'closed' | 'current') export interface XRP { currency: 'XRP' - issuer: never + issuer?: never } export interface IssuedCurrency { @@ -148,3 +148,10 @@ export interface AuthAccount { Account: string } } + +export interface XChainBridge { + LockingChainDoor: string + LockingChainIssue: Currency + IssuingChainDoor: string + IssuingChainIssue: Currency +} diff --git a/packages/xrpl/src/models/ledger/Bridge.ts b/packages/xrpl/src/models/ledger/Bridge.ts new file mode 100644 index 0000000000..51be5bb441 --- /dev/null +++ b/packages/xrpl/src/models/ledger/Bridge.ts @@ -0,0 +1,84 @@ +import { Amount, XChainBridge } from '../common' + +import BaseLedgerEntry from './BaseLedgerEntry' + +/** + * A Bridge objects represents a cross-chain bridge and includes information about + * the door accounts, assets, signature rewards, and the minimum account create + * amount. + * + * @category Ledger Entries + */ +export default interface Bridge extends BaseLedgerEntry { + LedgerEntryType: 'Bridge' + + /** The door account that owns the bridge. */ + Account: string + + /** + * The total amount, in XRP, to be rewarded for providing a signature for + * cross-chain transfer or for signing for the cross-chain reward. This amount + * will be split among the signers. + */ + SignatureReward: Amount + + /** + * The minimum amount, in XRP, required for an {@link XChainAccountCreateCommit} + * transaction. If this isn't present, the {@link XChainAccountCreateCommit} + * transaction will fail. This field can only be present on XRP-XRP bridges. + */ + MinAccountCreateAmount?: string + + /** + * The door accounts and assets of the bridge this object correlates to. + */ + XChainBridge: XChainBridge + + /** + * The value of the next XChainClaimID to be created. + */ + XChainClaimID: string + + /** + * A counter used to order the execution of account create transactions. It is + * incremented every time a successful {@link XChainAccountCreateCommit} + * transaction is run for the source chain. + */ + XChainAccountCreateCount: string + + /** + * A counter used to order the execution of account create transactions. It is + * incremented every time a {@link XChainAccountCreateCommit} transaction is + * "claimed" on the destination chain. When the "claim" transaction is run on + * the destination chain, the XChainAccountClaimCount must match the value that + * the XChainAccountCreateCount had at the time the XChainAccountClaimCount was + * run on the source chain. This orders the claims so that they run in the same + * order that the XChainAccountCreateCommit transactions ran on the source chain, + * to prevent transaction replay. + */ + XChainAccountClaimCount: string + + /** + * A bit-map of boolean flags. No flags are defined for Bridges, so this value + * is always 0. + */ + Flags: 0 + + /** + * A hint indicating which page of the sender's owner directory links to this + * object, in case the directory consists of multiple pages. + */ + OwnerNode: string + + /** + * The identifying hash of the transaction that most recently modified this + * object. + */ + PreviousTxnID: string + + /** + * The index of the ledger that contains the transaction that most recently + * modified this object. + */ + PreviousTxnLgrSeq: number +} diff --git a/packages/xrpl/src/models/ledger/LedgerEntry.ts b/packages/xrpl/src/models/ledger/LedgerEntry.ts index c09883fb17..954a85aabd 100644 --- a/packages/xrpl/src/models/ledger/LedgerEntry.ts +++ b/packages/xrpl/src/models/ledger/LedgerEntry.ts @@ -1,6 +1,7 @@ import AccountRoot from './AccountRoot' import Amendments from './Amendments' import AMM from './AMM' +import Bridge from './Bridge' import Check from './Check' import DepositPreauth from './DepositPreauth' import DirectoryNode from './DirectoryNode' @@ -13,11 +14,14 @@ import PayChannel from './PayChannel' import RippleState from './RippleState' import SignerList from './SignerList' import Ticket from './Ticket' +import XChainOwnedClaimID from './XChainOwnedClaimID' +import XChainOwnedCreateAccountClaimID from './XChainOwnedCreateAccountClaimID' type LedgerEntry = | AccountRoot | Amendments | AMM + | Bridge | Check | DepositPreauth | DirectoryNode @@ -30,5 +34,7 @@ type LedgerEntry = | RippleState | SignerList | Ticket + | XChainOwnedClaimID + | XChainOwnedCreateAccountClaimID export default LedgerEntry diff --git a/packages/xrpl/src/models/ledger/XChainOwnedClaimID.ts b/packages/xrpl/src/models/ledger/XChainOwnedClaimID.ts new file mode 100644 index 0000000000..bc33d48a31 --- /dev/null +++ b/packages/xrpl/src/models/ledger/XChainOwnedClaimID.ts @@ -0,0 +1,89 @@ +import { Amount } from 'ripple-binary-codec/dist/types' + +import { XChainBridge } from '../common' + +import BaseLedgerEntry from './BaseLedgerEntry' + +/** + * An XChainOwnedClaimID object represents one cross-chain transfer of value + * and includes information of the account on the source chain that locks or + * burns the funds on the source chain. + * + * @category Ledger Entries + */ +export default interface XChainOwnedClaimID extends BaseLedgerEntry { + LedgerEntryType: 'XChainOwnedClaimID' + + /** The account that checked out this unique claim ID value. */ + Account: string + + /** + * The door accounts and assets of the bridge this object correlates to. + */ + XChainBridge: XChainBridge + + /** + * The unique sequence number for a cross-chain transfer. + */ + XChainClaimID: string + + /** + * The account that must send the corresponding {@link XChainCommit} on the + * source chain. The destination may be specified in the {@link XChainCommit} + * transaction, which means that if the OtherChainSource isn't specified, + * another account can try to specify a different destination and steal the + * funds. This also allows tracking only a single set of signatures, since we + * know which account will send the {@link XChainCommit} transaction. + */ + OtherChainSource: string + + /** + * Attestations collected from the witness servers. This includes the parameters + * needed to recreate the message that was signed, including the amount, which + * chain (locking or issuing), optional destination, and reward account for that + * signature. + */ + XChainClaimAttestations: Array<{ + // TODO: add docs + XChainClaimProofSig: { + Amount: Amount + + AttestationRewardAccount: string + + AttestationSignerAccount: string + + Destination?: string + + PublicKey: string + + WasLockingChainSend: 0 | 1 + } + }> + + /** + * The total amount to pay the witness servers for their signatures. It must be at + * least the value of the SignatureReward in the {@link Bridge} ledger object. + */ + SignatureReward: string + + /** + * A bit-map of boolean flags. No flags are defined for XChainOwnedClaimIDs, + * so this value is always 0. + */ + Flags: 0 + /** + * A hint indicating which page of the sender's owner directory links to this + * object, in case the directory consists of multiple pages. + */ + OwnerNode: string + /** + * The identifying hash of the transaction that most recently modified this + * object. + */ + PreviousTxnID: string + /** + * The index of the ledger that contains the transaction that most recently + * modified this object. + */ + PreviousTxnLgrSeq: number +} diff --git a/packages/xrpl/src/models/ledger/XChainOwnedCreateAccountClaimID.ts b/packages/xrpl/src/models/ledger/XChainOwnedCreateAccountClaimID.ts new file mode 100644 index 0000000000..85f4c43728 --- /dev/null +++ b/packages/xrpl/src/models/ledger/XChainOwnedCreateAccountClaimID.ts @@ -0,0 +1,74 @@ +import { XChainBridge } from '../common' + +import BaseLedgerEntry from './BaseLedgerEntry' + +/** + * The XChainOwnedCreateAccountClaimID ledger object is used to collect attestations + * for creating an account via a cross-chain transfer. + * + * @category Ledger Entries + */ +export default interface XChainOwnedCreateAccountClaimID + extends BaseLedgerEntry { + LedgerEntryType: 'XChainOwnedCreateAccountClaimID' + + /** The account that owns this object. */ + Account: string + + /** + * The door accounts and assets of the bridge this object correlates to. + */ + XChainBridge: XChainBridge + + /** + * An integer that determines the order that accounts created through + * cross-chain transfers must be performed. Smaller numbers must execute + * before larger numbers. + */ + XChainAccountCreateCount: number + + /** + * Attestations collected from the witness servers. This includes the parameters + * needed to recreate the message that was signed, including the amount, destination, + * signature reward amount, and reward account for that signature. With the + * exception of the reward account, all signatures must sign the message created with + * common parameters. + */ + XChainCreateAccountAttestations: Array<{ + // TODO: add docs + XChainCreateAccountProofSig: { + Amount: string + + AttestationRewardAccount: string + + AttestationSignerAccount: string + + Destination: string + + PublicKey: string + + WasLockingChainSend: 0 | 1 + } + }> + + /** + * A bit-map of boolean flags. No flags are defined for, + * XChainOwnedCreateAccountClaimIDs, so this value is always 0. + */ + Flags: 0 + /** + * A hint indicating which page of the sender's owner directory links to this + * object, in case the directory consists of multiple pages. + */ + OwnerNode: string + /** + * The identifying hash of the transaction that most recently modified this + * object. + */ + PreviousTxnID: string + /** + * The index of the ledger that contains the transaction that most recently + * modified this object. + */ + PreviousTxnLgrSeq: number +} diff --git a/packages/xrpl/src/models/ledger/index.ts b/packages/xrpl/src/models/ledger/index.ts index 58a735bd74..25323b1268 100644 --- a/packages/xrpl/src/models/ledger/index.ts +++ b/packages/xrpl/src/models/ledger/index.ts @@ -4,6 +4,7 @@ import AccountRoot, { } from './AccountRoot' import Amendments, { Majority, AMENDMENTS_ID } from './Amendments' import AMM, { VoteSlot } from './AMM' +import Bridge from './Bridge' import Check from './Check' import DepositPreauth from './DepositPreauth' import DirectoryNode from './DirectoryNode' @@ -24,6 +25,8 @@ import PayChannel from './PayChannel' import RippleState, { RippleStateFlags } from './RippleState' import SignerList, { SignerListFlags } from './SignerList' import Ticket from './Ticket' +import XChainOwnedClaimID from './XChainOwnedClaimID' +import XChainOwnedCreateAccountClaimID from './XChainOwnedCreateAccountClaimID' export { AccountRoot, @@ -32,6 +35,7 @@ export { AMENDMENTS_ID, Amendments, AMM, + Bridge, Check, DepositPreauth, DirectoryNode, @@ -57,5 +61,7 @@ export { SignerList, SignerListFlags, Ticket, + XChainOwnedClaimID, + XChainOwnedCreateAccountClaimID, VoteSlot, } diff --git a/packages/xrpl/src/models/methods/accountObjects.ts b/packages/xrpl/src/models/methods/accountObjects.ts index 080d705ae7..d95bc0afb1 100644 --- a/packages/xrpl/src/models/methods/accountObjects.ts +++ b/packages/xrpl/src/models/methods/accountObjects.ts @@ -1,5 +1,6 @@ import { AMM, + Bridge, Check, DepositPreauth, Escrow, @@ -8,12 +9,15 @@ import { RippleState, SignerList, Ticket, + XChainOwnedClaimID, + XChainOwnedCreateAccountClaimID, } from '../ledger' import { BaseRequest, BaseResponse, LookupByLedgerRequest } from './baseMethod' export type AccountObjectType = | 'amm' + | 'bridge' | 'check' | 'deposit_preauth' | 'escrow' @@ -23,6 +27,8 @@ export type AccountObjectType = | 'signer_list' | 'state' | 'ticket' + | 'xchain_owned_create_account_claim_id' + | 'xchain_owned_claim_id' /** * The account_objects command returns the raw ledger format for all objects @@ -67,6 +73,7 @@ export interface AccountObjectsRequest */ export type AccountObject = | AMM + | Bridge | Check | DepositPreauth | Escrow @@ -75,6 +82,8 @@ export type AccountObject = | SignerList | RippleState | Ticket + | XChainOwnedClaimID + | XChainOwnedCreateAccountClaimID /** * Response expected from an {@link AccountObjectsRequest}. diff --git a/packages/xrpl/src/models/methods/ledgerEntry.ts b/packages/xrpl/src/models/methods/ledgerEntry.ts index acd44d69de..5068b6ca29 100644 --- a/packages/xrpl/src/models/methods/ledgerEntry.ts +++ b/packages/xrpl/src/models/methods/ledgerEntry.ts @@ -1,3 +1,4 @@ +import { Currency, XChainBridge } from '../common' import { LedgerEntry } from '../ledger' import { BaseRequest, BaseResponse, LookupByLedgerRequest } from './baseMethod' @@ -152,6 +153,30 @@ export interface LedgerEntryRequest extends BaseRequest, LookupByLedgerRequest { * Must be the object ID of the NFToken page, as hexadecimal */ nft_page?: string + + bridge_account?: string + + bridge?: XChainBridge + + xchain_owned_claim_id?: + | { + locking_chain_door: string + locking_chain_issue: Currency + issuing_chain_door: string + issuing_chain_issue: Currency + xchain_owned_claim_id: string | number + } + | string + + xchain_owned_create_account_claim_id?: + | { + locking_chain_door: string + locking_chain_issue: Currency + issuing_chain_door: string + issuing_chain_issue: Currency + xchain_owned_create_account_claim_id: string | number + } + | string } /** diff --git a/packages/xrpl/src/models/transactions/XChainAccountCreateCommit.ts b/packages/xrpl/src/models/transactions/XChainAccountCreateCommit.ts new file mode 100644 index 0000000000..1730ee6a33 --- /dev/null +++ b/packages/xrpl/src/models/transactions/XChainAccountCreateCommit.ts @@ -0,0 +1,69 @@ +import { isString } from 'lodash' + +import { Amount, XChainBridge } from '../common' + +import { + BaseTransaction, + isAmount, + isXChainBridge, + validateBaseTransaction, + validateRequiredField, +} from './common' + +/** + * The XChainAccountCreateCommit transaction creates a new account on one of the + * chains a bridge connects, which serves as the bridge entrance for that chain. + * + * Warning: This transaction should only be executed if the witness attestations + * will be reliably delivered to the destination chain. If the signatures aren't + * delivered, then account creation will be blocked until attestations are received. + * This can be used maliciously; to disable this transaction on XRP-XRP bridges, + * the bridge's MinAccountCreateAmount shouldn't be present. + * + * @category Transaction Models + */ +export interface XChainAccountCreateCommit extends BaseTransaction { + TransactionType: 'XChainAccountCreateCommit' + + /** + * The bridge to create accounts for. + */ + XChainBridge: XChainBridge + + /** + * The amount, in XRP, to be used to reward the witness servers for providing + * signatures. This must match the amount on the {@link Bridge} ledger object. + */ + SignatureReward: Amount + + /** + * The destination account on the destination chain. + */ + Destination: string + + /** + * The amount, in XRP, to use for account creation. This must be greater than or + * equal to the MinAccountCreateAmount specified in the {@link Bridge} ledger object. + */ + Amount: Amount +} + +/** + * Verify the form and type of an XChainAccountCreateCommit at runtime. + * + * @param tx - An XChainAccountCreateCommit Transaction. + * @throws When the XChainAccountCreateCommit is malformed. + */ +export function validateXChainAccountCreateCommit( + tx: Record, +): void { + validateBaseTransaction(tx) + + validateRequiredField(tx, 'XChainBridge', isXChainBridge) + + validateRequiredField(tx, 'SignatureReward', isAmount) + + validateRequiredField(tx, 'Destination', isString) + + validateRequiredField(tx, 'Amount', isAmount) +} diff --git a/packages/xrpl/src/models/transactions/XChainAddAccountCreateAttestation.ts b/packages/xrpl/src/models/transactions/XChainAddAccountCreateAttestation.ts new file mode 100644 index 0000000000..1e724403aa --- /dev/null +++ b/packages/xrpl/src/models/transactions/XChainAddAccountCreateAttestation.ts @@ -0,0 +1,121 @@ +import { Amount, XChainBridge } from '../common' + +import { + BaseTransaction, + isAmount, + isNumber, + isString, + isXChainBridge, + validateBaseTransaction, + validateRequiredField, +} from './common' + +/** + * The XChainAddAccountCreateAttestation transaction provides an attestation + * from a witness server that a {@link XChainAccountCreateCommit} transaction + * occurred on the other chain. + * + * @category Transaction Models + */ +export interface XChainAddAccountCreateAttestation extends BaseTransaction { + TransactionType: 'XChainAddAccountCreateAttestation' + + /** + * The amount committed by the {@link XChainAccountCreateCommit} transaction + * on the source chain. + */ + Amount: Amount + + /** + * The account that should receive this signer's share of the SignatureReward. + */ + AttestationRewardAccount: string + + /** + * The account on the door account's signer list that is signing the transaction. + */ + AttestationSignerAccount: string + + /** + * The destination account for the funds on the destination chain. + */ + Destination: string + + /** + * The account on the source chain that submitted the {@link XChainAccountCreateCommit} + * transaction that triggered the event associated with the attestation. + */ + OtherChainSource: string + + /** + * The public key used to verify the signature. + */ + PublicKey: string + + /** + * The signature attesting to the event on the other chain. + */ + Signature: string + + /** + * The signature reward paid in the {@link XChainAccountCreateCommit} transaction. + */ + SignatureReward: Amount + + /** + * A boolean representing the chain where the event occurred. + */ + WasLockingChainSend: 0 | 1 + + /** + * The counter that represents the order that the claims must be processed in. + */ + XChainAccountCreateCount: number | string + + /** + * The bridge associated with the attestation. + */ + XChainBridge: XChainBridge +} + +/** + * Verify the form and type of an XChainAddAccountCreateAttestation at runtime. + * + * @param tx - An XChainAddAccountCreateAttestation Transaction. + * @throws When the XChainAddAccountCreateAttestation is malformed. + */ +export function validateXChainAddAccountCreateAttestation( + tx: Record, +): void { + validateBaseTransaction(tx) + + validateRequiredField(tx, 'Amount', isAmount) + + validateRequiredField(tx, 'AttestationRewardAccount', isString) + + validateRequiredField(tx, 'AttestationSignerAccount', isString) + + validateRequiredField(tx, 'Destination', isString) + + validateRequiredField(tx, 'OtherChainSource', isString) + + validateRequiredField(tx, 'PublicKey', isString) + + validateRequiredField(tx, 'Signature', isString) + + validateRequiredField(tx, 'SignatureReward', isAmount) + + validateRequiredField( + tx, + 'WasLockingChainSend', + (inp) => inp === 0 || inp === 1, + ) + + validateRequiredField( + tx, + 'XChainAccountCreateCount', + (inp) => isNumber(inp) || isString(inp), + ) + + validateRequiredField(tx, 'XChainBridge', isXChainBridge) +} diff --git a/packages/xrpl/src/models/transactions/XChainAddClaimAttestation.ts b/packages/xrpl/src/models/transactions/XChainAddClaimAttestation.ts new file mode 100644 index 0000000000..ba309cf8a7 --- /dev/null +++ b/packages/xrpl/src/models/transactions/XChainAddClaimAttestation.ts @@ -0,0 +1,115 @@ +import { Amount, XChainBridge } from '../common' + +import { + BaseTransaction, + isAmount, + isNumber, + isString, + isXChainBridge, + validateBaseTransaction, + validateOptionalField, + validateRequiredField, +} from './common' + +/** + * The XChainAddClaimAttestation transaction provides proof from a witness server, + * attesting to an {@link XChainCommit} transaction. + * + * @category Transaction Models + */ +export interface XChainAddClaimAttestation extends BaseTransaction { + TransactionType: 'XChainAddClaimAttestation' + + /** + * The amount committed by the {@link XChainCommit} transaction on the source chain. + */ + Amount: Amount + + /** + * The account that should receive this signer's share of the SignatureReward. + */ + AttestationRewardAccount: string + + /** + * The account on the door account's signer list that is signing the transaction. + */ + AttestationSignerAccount: string + + /** + * The destination account for the funds on the destination chain (taken from + * the {@link XChainCommit} transaction). + */ + Destination?: string + + /** + * The account on the source chain that submitted the {@link XChainCommit} + * transaction that triggered the event associated with the attestation. + */ + OtherChainSource: string + + /** + * The public key used to verify the attestation signature. + */ + PublicKey: string + + /** + * The signature attesting to the event on the other chain. + */ + Signature: string + + /** + * A boolean representing the chain where the event occurred. + */ + WasLockingChainSend: 0 | 1 + + /** + * The bridge to use to transfer funds. + */ + XChainBridge: XChainBridge + + /** + * The XChainClaimID associated with the transfer, which was included in the + * {@link XChainCommit} transaction. + */ + XChainClaimID: number | string +} + +/** + * Verify the form and type of an XChainAddClaimAttestation at runtime. + * + * @param tx - An XChainAddClaimAttestation Transaction. + * @throws When the XChainAddClaimAttestation is malformed. + */ +export function validateXChainAddClaimAttestation( + tx: Record, +): void { + validateBaseTransaction(tx) + + validateRequiredField(tx, 'Amount', isAmount) + + validateRequiredField(tx, 'AttestationRewardAccount', isString) + + validateRequiredField(tx, 'AttestationSignerAccount', isString) + + validateOptionalField(tx, 'Destination', isString) + + validateRequiredField(tx, 'OtherChainSource', isString) + + validateRequiredField(tx, 'PublicKey', isString) + + validateRequiredField(tx, 'Signature', isString) + + validateRequiredField( + tx, + 'WasLockingChainSend', + (inp) => inp === 0 || inp === 1, + ) + + validateRequiredField(tx, 'XChainBridge', isXChainBridge) + + validateRequiredField( + tx, + 'XChainClaimID', + (inp) => isNumber(inp) || isString(inp), + ) +} diff --git a/packages/xrpl/src/models/transactions/XChainClaim.ts b/packages/xrpl/src/models/transactions/XChainClaim.ts new file mode 100644 index 0000000000..e78bcecb97 --- /dev/null +++ b/packages/xrpl/src/models/transactions/XChainClaim.ts @@ -0,0 +1,77 @@ +import { Amount, XChainBridge } from '../common' + +import { + BaseTransaction, + isAmount, + isNumber, + isString, + isXChainBridge, + validateBaseTransaction, + validateOptionalField, + validateRequiredField, +} from './common' + +/** + * The XChainClaim transaction completes a cross-chain transfer of value. It + * allows a user to claim the value on the destination chain - the equivalent + * of the value locked on the source chain. + * + * @category Transaction Models + */ +export interface XChainClaim extends BaseTransaction { + TransactionType: 'XChainClaim' + + /** + * The bridge to use for the transfer. + */ + XChainBridge: XChainBridge + + /** + * The unique integer ID for the cross-chain transfer that was referenced in the + * corresponding {@link XChainCommit} transaction. + */ + XChainClaimID: number | string + + /** + * The destination account on the destination chain. It must exist or the + * transaction will fail. However, if the transaction fails in this case, the + * sequence number and collected signatures won't be destroyed, and the + * transaction can be rerun with a different destination. + */ + Destination: string + + /** + * An integer destination tag. + */ + DestinationTag?: number + + /** + * The amount to claim on the destination chain. This must match the amount + * attested to on the attestations associated with this XChainClaimID. + */ + Amount: Amount +} + +/** + * Verify the form and type of an XChainClaim at runtime. + * + * @param tx - An XChainClaim Transaction. + * @throws When the XChainClaim is malformed. + */ +export function validateXChainClaim(tx: Record): void { + validateBaseTransaction(tx) + + validateRequiredField(tx, 'XChainBridge', isXChainBridge) + + validateRequiredField( + tx, + 'XChainClaimID', + (inp) => isNumber(inp) || isString(inp), + ) + + validateRequiredField(tx, 'Destination', isString) + + validateOptionalField(tx, 'DestinationTag', isNumber) + + validateRequiredField(tx, 'Amount', isAmount) +} diff --git a/packages/xrpl/src/models/transactions/XChainCommit.ts b/packages/xrpl/src/models/transactions/XChainCommit.ts new file mode 100644 index 0000000000..3205fc7dd9 --- /dev/null +++ b/packages/xrpl/src/models/transactions/XChainCommit.ts @@ -0,0 +1,74 @@ +import { Amount, XChainBridge } from '../common' + +import { + BaseTransaction, + isAmount, + isNumber, + isString, + isXChainBridge, + validateBaseTransaction, + validateOptionalField, + validateRequiredField, +} from './common' + +/** + * The XChainCommit is the second step in a cross-chain transfer. It puts assets + * into trust on the locking chain so that they can be wrapped on the issuing + * chain, or burns wrapped assets on the issuing chain so that they can be returned + * on the locking chain. + * + * @category Transaction Models + */ +export interface XChainCommit extends BaseTransaction { + TransactionType: 'XChainCommit' + + /** + * The bridge to use to transfer funds. + */ + XChainBridge: XChainBridge + + /** + * The unique integer ID for a cross-chain transfer. This must be acquired on + * the destination chain (via a {@link XChainCreateClaimID} transaction) and + * checked from a validated ledger before submitting this transaction. If an + * incorrect sequence number is specified, the funds will be lost. + */ + XChainClaimID: number | string + + /** + * The destination account on the destination chain. If this is not specified, + * the account that submitted the {@link XChainCreateClaimID} transaction on the + * destination chain will need to submit a {@link XChainClaim} transaction to + * claim the funds. + */ + OtherChainDestination?: string + + /** + * The asset to commit, and the quantity. This must match the door account's + * LockingChainIssue (if on the locking chain) or the door account's + * IssuingChainIssue (if on the issuing chain). + */ + Amount: Amount +} + +/** + * Verify the form and type of an XChainCommit at runtime. + * + * @param tx - An XChainCommit Transaction. + * @throws When the XChainCommit is malformed. + */ +export function validateXChainCommit(tx: Record): void { + validateBaseTransaction(tx) + + validateRequiredField(tx, 'XChainBridge', isXChainBridge) + + validateRequiredField( + tx, + 'XChainClaimID', + (inp) => isNumber(inp) || isString(inp), + ) + + validateOptionalField(tx, 'OtherChainDestination', isString) + + validateRequiredField(tx, 'Amount', isAmount) +} diff --git a/packages/xrpl/src/models/transactions/XChainCreateBridge.ts b/packages/xrpl/src/models/transactions/XChainCreateBridge.ts new file mode 100644 index 0000000000..e9d638bfe8 --- /dev/null +++ b/packages/xrpl/src/models/transactions/XChainCreateBridge.ts @@ -0,0 +1,56 @@ +import { Amount, XChainBridge } from '../common' + +import { + BaseTransaction, + isAmount, + isXChainBridge, + validateBaseTransaction, + validateOptionalField, + validateRequiredField, +} from './common' + +/** + * The XChainCreateBridge transaction creates a new {@link Bridge} ledger object + * and defines a new cross-chain bridge entrance on the chain that the transaction + * is submitted on. It includes information about door accounts and assets for the + * bridge. + * + * @category Transaction Models + */ +export interface XChainCreateBridge extends BaseTransaction { + TransactionType: 'XChainCreateBridge' + + /** + * The bridge (door accounts and assets) to create. + */ + XChainBridge: XChainBridge + + /** + * The total amount to pay the witness servers for their signatures. This amount + * will be split among the signers. + */ + SignatureReward: Amount + + /** + * The minimum amount, in XRP, required for a {@link XChainAccountCreateCommit} + * transaction. If this isn't present, the {@link XChainAccountCreateCommit} + * transaction will fail. This field can only be present on XRP-XRP bridges. + */ + MinAccountCreateAmount?: Amount +} + +/** + * Verify the form and type of an XChainCreateBridge at runtime. + * + * @param tx - An XChainCreateBridge Transaction. + * @throws When the XChainCreateBridge is malformed. + */ +export function validateXChainCreateBridge(tx: Record): void { + validateBaseTransaction(tx) + + validateRequiredField(tx, 'XChainBridge', isXChainBridge) + + validateRequiredField(tx, 'SignatureReward', isAmount) + + validateOptionalField(tx, 'MinAccountCreateAmount', isAmount) +} diff --git a/packages/xrpl/src/models/transactions/XChainCreateClaimID.ts b/packages/xrpl/src/models/transactions/XChainCreateClaimID.ts new file mode 100644 index 0000000000..2fa696fde9 --- /dev/null +++ b/packages/xrpl/src/models/transactions/XChainCreateClaimID.ts @@ -0,0 +1,53 @@ +import { Amount, XChainBridge } from '../common' + +import { + BaseTransaction, + isAmount, + isString, + isXChainBridge, + validateBaseTransaction, + validateRequiredField, +} from './common' + +/** + * The XChainCreateClaimID transaction creates a new cross-chain claim ID that is + * used for a cross-chain transfer. A cross-chain claim ID represents one + * cross-chain transfer of value. + * + * @category Transaction Models + */ +export interface XChainCreateClaimID extends BaseTransaction { + TransactionType: 'XChainCreateClaimID' + + /** + * The bridge to create the claim ID for. + */ + XChainBridge: XChainBridge + + /** + * The amount, in XRP, to reward the witness servers for providing signatures. + * This must match the amount on the {@link Bridge} ledger object. + */ + SignatureReward: Amount + + /** + * The account that must send the {@link XChainCommit} transaction on the source chain. + */ + OtherChainSource: string +} + +/** + * Verify the form and type of an XChainCreateClaimID at runtime. + * + * @param tx - An XChainCreateClaimID Transaction. + * @throws When the XChainCreateClaimID is malformed. + */ +export function validateXChainCreateClaimID(tx: Record): void { + validateBaseTransaction(tx) + + validateRequiredField(tx, 'XChainBridge', isXChainBridge) + + validateRequiredField(tx, 'SignatureReward', isAmount) + + validateRequiredField(tx, 'OtherChainSource', isString) +} diff --git a/packages/xrpl/src/models/transactions/XChainModifyBridge.ts b/packages/xrpl/src/models/transactions/XChainModifyBridge.ts new file mode 100644 index 0000000000..841960b8f0 --- /dev/null +++ b/packages/xrpl/src/models/transactions/XChainModifyBridge.ts @@ -0,0 +1,77 @@ +import { Amount, XChainBridge } from '../common' + +import { + BaseTransaction, + GlobalFlags, + isAmount, + isXChainBridge, + validateBaseTransaction, + validateOptionalField, + validateRequiredField, +} from './common' + +/** + * Enum representing values of {@link XChainModifyBridge} transaction flags. + * + * @category Transaction Flags + */ +export enum XChainModifyBridgeFlags { + /** Clears the MinAccountCreateAmount of the bridge. */ + tfClearAccountCreateAmount = 0x00010000, +} + +/** + * Map of flags to boolean values representing {@link XChainModifyBridge} transaction + * flags. + * + * @category Transaction Flags + */ +export interface XChainModifyBridgeFlagsInterface extends GlobalFlags { + /** Clears the MinAccountCreateAmount of the bridge. */ + tfClearAccountCreateAmount?: boolean +} + +/** + * The XChainModifyBridge transaction allows bridge managers to modify the parameters + * of the bridge. + * + * @category Transaction Models + */ +export interface XChainModifyBridge extends BaseTransaction { + TransactionType: 'XChainModifyBridge' + + /** + * The bridge to modify. + */ + XChainBridge: XChainBridge + + /** + * The signature reward split between the witnesses for submitting attestations. + */ + SignatureReward?: Amount + + /** + * The minimum amount, in XRP, required for a {@link XChainAccountCreateCommit} + * transaction. If this is not present, the {@link XChainAccountCreateCommit} + * transaction will fail. This field can only be present on XRP-XRP bridges. + */ + MinAccountCreateAmount?: Amount + + Flags?: number | XChainModifyBridgeFlagsInterface +} + +/** + * Verify the form and type of an XChainModifyBridge at runtime. + * + * @param tx - An XChainModifyBridge Transaction. + * @throws When the XChainModifyBridge is malformed. + */ +export function validateXChainModifyBridge(tx: Record): void { + validateBaseTransaction(tx) + + validateRequiredField(tx, 'XChainBridge', isXChainBridge) + + validateOptionalField(tx, 'SignatureReward', isAmount) + + validateOptionalField(tx, 'MinAccountCreateAmount', isAmount) +} diff --git a/packages/xrpl/src/models/transactions/common.ts b/packages/xrpl/src/models/transactions/common.ts index 9781e52497..78ce7455d9 100644 --- a/packages/xrpl/src/models/transactions/common.ts +++ b/packages/xrpl/src/models/transactions/common.ts @@ -1,9 +1,14 @@ -/* eslint-disable max-lines-per-function -- Necessary for validateBaseTransaction */ -/* eslint-disable max-statements -- Necessary for validateBaseTransaction */ import { TRANSACTION_TYPES } from 'ripple-binary-codec' import { ValidationError } from '../../errors' -import { Amount, Currency, IssuedCurrencyAmount, Memo, Signer } from '../common' +import { + Amount, + Currency, + IssuedCurrencyAmount, + Memo, + Signer, + XChainBridge, +} from '../common' import { onlyHasFields } from '../utils' const MEMO_SIZE = 3 @@ -52,11 +57,32 @@ function isSigner(obj: unknown): boolean { const XRP_CURRENCY_SIZE = 1 const ISSUE_SIZE = 2 const ISSUED_CURRENCY_SIZE = 3 +const XCHAIN_BRIDGE_SIZE = 4 function isRecord(value: unknown): value is Record { return value !== null && typeof value === 'object' } +/** + * Verify the form and type of a string at runtime. + * + * @param str - The object to check the form and type of. + * @returns Whether the string is properly formed. + */ +export function isString(str: unknown): str is string { + return typeof str === 'string' +} + +/** + * Verify the form and type of a number at runtime. + * + * @param num - The object to check the form and type of. + * @returns Whether the number is properly formed. + */ +export function isNumber(num: unknown): num is number { + return typeof num === 'number' +} + /** * Verify the form and type of an IssuedCurrency at runtime. * @@ -102,6 +128,73 @@ export function isAmount(amount: unknown): amount is Amount { return typeof amount === 'string' || isIssuedCurrency(amount) } +/** + * Verify the form and type of an XChainBridge at runtime. + * + * @param input - The input to check the form and type of. + * @returns Whether the XChainBridge is properly formed. + */ +export function isXChainBridge(input: unknown): input is XChainBridge { + return ( + isRecord(input) && + Object.keys(input).length === XCHAIN_BRIDGE_SIZE && + typeof input.LockingChainDoor === 'string' && + isCurrency(input.LockingChainIssue) && + typeof input.IssuingChainDoor === 'string' && + isCurrency(input.IssuingChainIssue) + ) +} + +/* eslint-disable @typescript-eslint/restrict-template-expressions -- tx.TransactionType is checked before any calls */ + +/** + * Verify the form and type of a required type for a transaction at runtime. + * + * @param tx - The transaction input to check the form and type of. + * @param paramName - The name of the transaction parameter. + * @param checkValidity - The function to use to check the type. + * @throws + */ +export function validateRequiredField( + tx: Record, + paramName: string, + checkValidity: (inp: unknown) => boolean, +): void { + if (tx[paramName] == null) { + throw new ValidationError( + `${tx.TransactionType}: missing field ${paramName}`, + ) + } + + if (!checkValidity(tx[paramName])) { + throw new ValidationError( + `${tx.TransactionType}: invalid field ${paramName}`, + ) + } +} + +/** + * Verify the form and type of an optional type for a transaction at runtime. + * + * @param tx - The transaction input to check the form and type of. + * @param paramName - The name of the transaction parameter. + * @param checkValidity - The function to use to check the type. + * @throws + */ +export function validateOptionalField( + tx: Record, + paramName: string, + checkValidity: (inp: unknown) => boolean, +): void { + if (tx[paramName] !== undefined && !checkValidity(tx[paramName])) { + throw new ValidationError( + `${tx.TransactionType}: invalid field ${paramName}`, + ) + } +} + +/* eslint-enable @typescript-eslint/restrict-template-expressions -- checked before */ + // eslint-disable-next-line @typescript-eslint/no-empty-interface -- no global flags right now, so this is fine export interface GlobalFlags {} @@ -190,14 +283,6 @@ export interface BaseTransaction { * @throws When the common param is malformed. */ export function validateBaseTransaction(common: Record): void { - if (common.Account === undefined) { - throw new ValidationError('BaseTransaction: missing field Account') - } - - if (typeof common.Account !== 'string') { - throw new ValidationError('BaseTransaction: Account not string') - } - if (common.TransactionType === undefined) { throw new ValidationError('BaseTransaction: missing field TransactionType') } @@ -210,27 +295,15 @@ export function validateBaseTransaction(common: Record): void { throw new ValidationError('BaseTransaction: Unknown TransactionType') } - if (common.Fee !== undefined && typeof common.Fee !== 'string') { - throw new ValidationError('BaseTransaction: invalid Fee') - } + validateRequiredField(common, 'Account', isString) - if (common.Sequence !== undefined && typeof common.Sequence !== 'number') { - throw new ValidationError('BaseTransaction: invalid Sequence') - } + validateOptionalField(common, 'Fee', isString) - if ( - common.AccountTxnID !== undefined && - typeof common.AccountTxnID !== 'string' - ) { - throw new ValidationError('BaseTransaction: invalid AccountTxnID') - } + validateOptionalField(common, 'Sequence', isNumber) - if ( - common.LastLedgerSequence !== undefined && - typeof common.LastLedgerSequence !== 'number' - ) { - throw new ValidationError('BaseTransaction: invalid LastLedgerSequence') - } + validateOptionalField(common, 'AccountTxnID', isString) + + validateOptionalField(common, 'LastLedgerSequence', isNumber) // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Only used by JS const memos = common.Memos as Array<{ Memo?: unknown }> | undefined @@ -248,33 +321,15 @@ export function validateBaseTransaction(common: Record): void { throw new ValidationError('BaseTransaction: invalid Signers') } - if (common.SourceTag !== undefined && typeof common.SourceTag !== 'number') { - throw new ValidationError('BaseTransaction: invalid SourceTag') - } + validateOptionalField(common, 'SourceTag', isNumber) - if ( - common.SigningPubKey !== undefined && - typeof common.SigningPubKey !== 'string' - ) { - throw new ValidationError('BaseTransaction: invalid SigningPubKey') - } + validateOptionalField(common, 'SigningPubKey', isString) - if ( - common.TicketSequence !== undefined && - typeof common.TicketSequence !== 'number' - ) { - throw new ValidationError('BaseTransaction: invalid TicketSequence') - } + validateOptionalField(common, 'TicketSequence', isNumber) - if ( - common.TxnSignature !== undefined && - typeof common.TxnSignature !== 'string' - ) { - throw new ValidationError('BaseTransaction: invalid TxnSignature') - } - if (common.NetworkID !== undefined && typeof common.NetworkID !== 'number') { - throw new ValidationError('BaseTransaction: invalid NetworkID') - } + validateOptionalField(common, 'TxnSignature', isString) + + validateOptionalField(common, 'NetworkID', isNumber) } /** diff --git a/packages/xrpl/src/models/transactions/index.ts b/packages/xrpl/src/models/transactions/index.ts index f4ac17162b..577d8a3829 100644 --- a/packages/xrpl/src/models/transactions/index.ts +++ b/packages/xrpl/src/models/transactions/index.ts @@ -25,6 +25,7 @@ export { export { CheckCancel } from './checkCancel' export { CheckCash } from './checkCash' export { CheckCreate } from './checkCreate' +export { Clawback } from './clawback' export { DepositPreauth } from './depositPreauth' export { EscrowCancel } from './escrowCancel' export { EscrowCreate } from './escrowCreate' @@ -63,4 +64,15 @@ export { SignerListSet } from './signerListSet' export { TicketCreate } from './ticketCreate' export { TrustSetFlagsInterface, TrustSetFlags, TrustSet } from './trustSet' export { UNLModify } from './UNLModify' -export { Clawback } from './clawback' +export { XChainAddAccountCreateAttestation } from './XChainAddAccountCreateAttestation' +export { XChainAddClaimAttestation } from './XChainAddClaimAttestation' +export { XChainClaim } from './XChainClaim' +export { XChainCommit } from './XChainCommit' +export { XChainCreateBridge } from './XChainCreateBridge' +export { XChainCreateClaimID } from './XChainCreateClaimID' +export { XChainAccountCreateCommit } from './XChainAccountCreateCommit' +export { + XChainModifyBridge, + XChainModifyBridgeFlags, + XChainModifyBridgeFlagsInterface, +} from './XChainModifyBridge' diff --git a/packages/xrpl/src/models/transactions/transaction.ts b/packages/xrpl/src/models/transactions/transaction.ts index 6c7fa4091d..ef47334729 100644 --- a/packages/xrpl/src/models/transactions/transaction.ts +++ b/packages/xrpl/src/models/transactions/transaction.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-lines -- need to work with a lot of transactions in a switch statement */ /* eslint-disable max-lines-per-function -- need to work with a lot of Tx verifications */ import { ValidationError } from '../../errors' @@ -56,6 +57,32 @@ import { SetRegularKey, validateSetRegularKey } from './setRegularKey' import { SignerListSet, validateSignerListSet } from './signerListSet' import { TicketCreate, validateTicketCreate } from './ticketCreate' import { TrustSet, validateTrustSet } from './trustSet' +import { + XChainAccountCreateCommit, + validateXChainAccountCreateCommit, +} from './XChainAccountCreateCommit' +import { + XChainAddAccountCreateAttestation, + validateXChainAddAccountCreateAttestation, +} from './XChainAddAccountCreateAttestation' +import { + XChainAddClaimAttestation, + validateXChainAddClaimAttestation, +} from './XChainAddClaimAttestation' +import { XChainClaim, validateXChainClaim } from './XChainClaim' +import { XChainCommit, validateXChainCommit } from './XChainCommit' +import { + XChainCreateBridge, + validateXChainCreateBridge, +} from './XChainCreateBridge' +import { + XChainCreateClaimID, + validateXChainCreateClaimID, +} from './XChainCreateClaimID' +import { + XChainModifyBridge, + validateXChainModifyBridge, +} from './XChainModifyBridge' /** * @category Transaction Models @@ -92,6 +119,14 @@ export type Transaction = | SignerListSet | TicketCreate | TrustSet + | XChainAddAccountCreateAttestation + | XChainAddClaimAttestation + | XChainClaim + | XChainCommit + | XChainCreateBridge + | XChainCreateClaimID + | XChainAccountCreateCommit + | XChainModifyBridge /** * @category Transaction Models @@ -294,6 +329,38 @@ export function validate(transaction: Record): void { validateTrustSet(tx) break + case 'XChainAddAccountCreateAttestation': + validateXChainAddAccountCreateAttestation(tx) + break + + case 'XChainAddClaimAttestation': + validateXChainAddClaimAttestation(tx) + break + + case 'XChainClaim': + validateXChainClaim(tx) + break + + case 'XChainCommit': + validateXChainCommit(tx) + break + + case 'XChainCreateBridge': + validateXChainCreateBridge(tx) + break + + case 'XChainCreateClaimID': + validateXChainCreateClaimID(tx) + break + + case 'XChainAccountCreateCommit': + validateXChainAccountCreateCommit(tx) + break + + case 'XChainModifyBridge': + validateXChainModifyBridge(tx) + break + default: throw new ValidationError( `Invalid field TransactionType: ${tx.TransactionType}`, diff --git a/packages/xrpl/src/models/utils/flags.ts b/packages/xrpl/src/models/utils/flags.ts index 8f7169ad60..11d81efc75 100644 --- a/packages/xrpl/src/models/utils/flags.ts +++ b/packages/xrpl/src/models/utils/flags.ts @@ -15,6 +15,7 @@ import { PaymentFlags } from '../transactions/payment' import { PaymentChannelClaimFlags } from '../transactions/paymentChannelClaim' import type { Transaction } from '../transactions/transaction' import { TrustSetFlags } from '../transactions/trustSet' +import { XChainModifyBridgeFlags } from '../transactions/XChainModifyBridge' import { isFlagEnabled } from '.' @@ -78,6 +79,9 @@ export function setTransactionFlagsToNumber(tx: Transaction): void { case 'TrustSet': tx.Flags = convertFlagsToNumber(tx.Flags, TrustSetFlags) return + case 'XChainModifyBridge': + tx.Flags = convertFlagsToNumber(tx.Flags, XChainModifyBridgeFlags) + return default: tx.Flags = 0 } diff --git a/packages/xrpl/src/utils/getXChainClaimID.ts b/packages/xrpl/src/utils/getXChainClaimID.ts new file mode 100644 index 0000000000..5e640a8c1f --- /dev/null +++ b/packages/xrpl/src/utils/getXChainClaimID.ts @@ -0,0 +1,64 @@ +import { decode } from 'ripple-binary-codec' + +import { + CreatedNode, + isCreatedNode, + TransactionMetadata, +} from '../models/transactions/metadata' + +/** + * Ensures that the metadata is in a deserialized format to parse. + * + * @param meta - the metadata from a `tx` method call. Can be in json format or binary format. + * @returns the metadata in a deserialized format. + */ +function ensureDecodedMeta( + meta: TransactionMetadata | string, +): TransactionMetadata { + if (typeof meta === 'string') { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Meta is either metadata or serialized metadata. + return decode(meta) as unknown as TransactionMetadata + } + return meta +} + +/** + * Gets the XChainClaimID value from the metadata of an `XChainCreateClaimID` transaction. + * + * @param meta - Metadata from the response to submitting and waiting for an XChainCreateClaimID transaction + * or from a `tx` method call. + * @returns The XChainClaimID for the minted NFT. + * @throws if meta is not TransactionMetadata. + */ +export default function getXChainClaimID( + meta: TransactionMetadata | string | undefined, +): string | undefined { + if (typeof meta !== 'string' && meta?.AffectedNodes === undefined) { + throw new TypeError(`Unable to parse the parameter given to getXChainClaimID. + 'meta' must be the metadata from an XChainCreateClaimID transaction. Received ${JSON.stringify( + meta, + )} instead.`) + } + + const decodedMeta = ensureDecodedMeta(meta) + + if (!decodedMeta.TransactionResult) { + throw new TypeError( + 'Cannot get XChainClaimID from un-validated transaction', + ) + } + + if (decodedMeta.TransactionResult !== 'tesSUCCESS') { + return undefined + } + + const createdNode = decodedMeta.AffectedNodes.find( + (node) => + isCreatedNode(node) && + node.CreatedNode.LedgerEntryType === 'XChainOwnedClaimID', + ) + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- necessary here + return (createdNode as CreatedNode).CreatedNode.NewFields + .XChainClaimID as string +} diff --git a/packages/xrpl/src/utils/index.ts b/packages/xrpl/src/utils/index.ts index 06fdb390d0..91524a3846 100644 --- a/packages/xrpl/src/utils/index.ts +++ b/packages/xrpl/src/utils/index.ts @@ -25,6 +25,7 @@ import { Transaction } from '../models/transactions/transaction' import { deriveKeypair, deriveAddress, deriveXAddress } from './derive' import getBalanceChanges from './getBalanceChanges' import getNFTokenID from './getNFTokenID' +import getXChainClaimID from './getXChainClaimID' import { hashSignedTx, hashTx, @@ -220,4 +221,5 @@ export { encodeForSigningClaim, getNFTokenID, parseNFTokenID, + getXChainClaimID, } diff --git a/packages/xrpl/test/fixtures/rippled/index.ts b/packages/xrpl/test/fixtures/rippled/index.ts index 1d48f12c1c..fba2b5aff3 100644 --- a/packages/xrpl/test/fixtures/rippled/index.ts +++ b/packages/xrpl/test/fixtures/rippled/index.ts @@ -25,8 +25,12 @@ import successSubmit from './submit.json' import successSubscribe from './subscribe.json' import errorSubscribe from './subscribeError.json' import transaction_entry from './transactionEntry.json' +import NFTokenMint from './tx/NFTokenMint.json' +import NFTokenMint2 from './tx/NFTokenMint2.json' import OfferCreateSell from './tx/offerCreateSell.json' import Payment from './tx/payment.json' +import XChainCreateClaimID from './tx/XChainCreateClaimID.json' +import XChainCreateClaimID2 from './tx/XChainCreateClaimID2.json' import unsubscribe from './unsubscribe.json' const submit = { @@ -89,8 +93,12 @@ const server_info = { } const tx = { + NFTokenMint, + NFTokenMint2, Payment, OfferCreateSell, + XChainCreateClaimID, + XChainCreateClaimID2, } const rippled = { diff --git a/packages/xrpl/test/fixtures/rippled/mintNFTMeta.json b/packages/xrpl/test/fixtures/rippled/tx/NFTokenMint.json similarity index 100% rename from packages/xrpl/test/fixtures/rippled/mintNFTMeta.json rename to packages/xrpl/test/fixtures/rippled/tx/NFTokenMint.json diff --git a/packages/xrpl/test/fixtures/rippled/mintNFTMeta2.json b/packages/xrpl/test/fixtures/rippled/tx/NFTokenMint2.json similarity index 100% rename from packages/xrpl/test/fixtures/rippled/mintNFTMeta2.json rename to packages/xrpl/test/fixtures/rippled/tx/NFTokenMint2.json diff --git a/packages/xrpl/test/fixtures/rippled/tx/XChainCreateClaimID.json b/packages/xrpl/test/fixtures/rippled/tx/XChainCreateClaimID.json new file mode 100644 index 0000000000..976c90d89d --- /dev/null +++ b/packages/xrpl/test/fixtures/rippled/tx/XChainCreateClaimID.json @@ -0,0 +1,118 @@ +{ + "tx": { + "Account": "rLVUz66tawieqTPAHuTyFTN6pLbHcXiTzd", + "Fee": "20", + "Flags": 2147483648, + "NetworkID": 2552, + "OtherChainSource": "rL5Zd9m5XEoGPddMwYY5H5C8ARcR47b6oM", + "Sequence": 1007784, + "SignatureReward": "100", + "SigningPubKey": "039E925058C740A5B73E49300FC205D058520DE37F2C63C4EE3A0D1B50C4E44080", + "TransactionType": "XChainCreateClaimID", + "TxnSignature": "304402201C6F95B9997FB63DCD9854664707C58C46AA3207612FE32366B77DA084786CAF02205752C58821D7FAFAE26F77DC10AC0AFDDCBCCF4FCBED90E6B8C4523A0EB3E008", + "XChainBridge": { + "IssuingChainDoor": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "IssuingChainIssue": { + "currency": "XRP" + }, + "LockingChainDoor": "rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4", + "LockingChainIssue": { + "currency": "XRP" + } + }, + "date": 1695324353000 + }, + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Flags": 0, + "MinAccountCreateAmount": "10000000", + "OwnerNode": "0", + "SignatureReward": "100", + "XChainAccountClaimCount": "e3", + "XChainAccountCreateCount": "0", + "XChainBridge": { + "IssuingChainDoor": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "IssuingChainIssue": { + "currency": "XRP" + }, + "LockingChainDoor": "rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4", + "LockingChainIssue": { + "currency": "XRP" + } + }, + "XChainClaimID": "b0" + }, + "LedgerEntryType": "Bridge", + "LedgerIndex": "114C0DC89656D1B0FB1F4A3426034C3FCE75BCE65D9574B5D96ABC2B24D6C8F1", + "PreviousFields": { + "XChainClaimID": "af" + }, + "PreviousTxnID": "3F6F3BBE584115D1A575AB24BA32B47184F2323B65DE5C8C8EE144A55115E0B9", + "PreviousTxnLgrSeq": 1027822 + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Flags": 0, + "Owner": "rLVUz66tawieqTPAHuTyFTN6pLbHcXiTzd", + "RootIndex": "6C1EA1A93D590E831CCC0EE2CBE26C146A3A6FD36F5854DC5E5AB5CE78FAE49C" + }, + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "6C1EA1A93D590E831CCC0EE2CBE26C146A3A6FD36F5854DC5E5AB5CE78FAE49C" + } + }, + { + "CreatedNode": { + "LedgerEntryType": "XChainOwnedClaimID", + "LedgerIndex": "A00BD77AE864509D796B39041AD48E9DEFEC9AF20E5C09CEF2F5DA41D6CFEB1E", + "NewFields": { + "Account": "rLVUz66tawieqTPAHuTyFTN6pLbHcXiTzd", + "OtherChainSource": "rL5Zd9m5XEoGPddMwYY5H5C8ARcR47b6oM", + "SignatureReward": "100", + "XChainBridge": { + "IssuingChainDoor": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "IssuingChainIssue": { + "currency": "XRP" + }, + "LockingChainDoor": "rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4", + "LockingChainIssue": { + "currency": "XRP" + } + }, + "XChainClaimID": "b0" + } + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Account": "rLVUz66tawieqTPAHuTyFTN6pLbHcXiTzd", + "Balance": "39999940", + "Flags": 0, + "OwnerCount": 3, + "Sequence": 1007785 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "FD919D0BAA90C759DA4C7130AEEF6AE7FA2AF074F5E867D40BCBE1ECD8D8D0EA", + "PreviousFields": { + "Balance": "39999960", + "OwnerCount": 2, + "Sequence": 1007784 + }, + "PreviousTxnID": "3F6F3BBE584115D1A575AB24BA32B47184F2323B65DE5C8C8EE144A55115E0B9", + "PreviousTxnLgrSeq": 1027822 + } + } + ], + "TransactionIndex": 0, + "TransactionResult": "tesSUCCESS" + }, + "hash": "998E76B9840DA5A6009592A2674D0166A9C4862193193AA46EA6B77A64781FB4", + "ledger_index": 1027837, + "date": 1695324353000 +} diff --git a/packages/xrpl/test/fixtures/rippled/tx/XChainCreateClaimID2.json b/packages/xrpl/test/fixtures/rippled/tx/XChainCreateClaimID2.json new file mode 100644 index 0000000000..1525f2aa05 --- /dev/null +++ b/packages/xrpl/test/fixtures/rippled/tx/XChainCreateClaimID2.json @@ -0,0 +1,118 @@ +{ + "tx": { + "Account": "rwmUSzi5Xp31AjMTEdbvxgWqLETcVNU6Fv", + "Fee": "12", + "Flags": 0, + "LastLedgerSequence": 1027798, + "NetworkID": 2552, + "OtherChainSource": "rBXdfZ7NVpdjRfYajPMpviGgq7HLDeuBdR", + "Sequence": 1027778, + "SignatureReward": "100", + "SigningPubKey": "EDDDD69DF802B8DB82D644EF92E2C1F06AC128A275CDFF86F013180D104ED39D3B", + "TransactionType": "XChainCreateClaimID", + "TxnSignature": "67BE63527EC8A0C872F23E2C4EB97C1F3E7D3FED6D10C8310B9235D3891B6B9343768A080E258F6C3687BFC4B7C5FD429ABB33654C99DE46471FD6F2A7035303", + "XChainBridge": { + "IssuingChainDoor": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "IssuingChainIssue": { + "currency": "XRP" + }, + "LockingChainDoor": "rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4", + "LockingChainIssue": { + "currency": "XRP" + } + }, + "date": 1695324182000 + }, + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Flags": 0, + "MinAccountCreateAmount": "10000000", + "OwnerNode": "0", + "SignatureReward": "100", + "XChainAccountClaimCount": "e2", + "XChainAccountCreateCount": "0", + "XChainBridge": { + "IssuingChainDoor": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "IssuingChainIssue": { + "currency": "XRP" + }, + "LockingChainDoor": "rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4", + "LockingChainIssue": { + "currency": "XRP" + } + }, + "XChainClaimID": "ac" + }, + "LedgerEntryType": "Bridge", + "LedgerIndex": "114C0DC89656D1B0FB1F4A3426034C3FCE75BCE65D9574B5D96ABC2B24D6C8F1", + "PreviousFields": { + "XChainClaimID": "ab" + }, + "PreviousTxnID": "80C33D1FB349D698CFDB1A85E8368557C5B7219B74DFCB2B05E0B10E2667F902", + "PreviousTxnLgrSeq": 1027779 + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Account": "rwmUSzi5Xp31AjMTEdbvxgWqLETcVNU6Fv", + "Balance": "19999988", + "Flags": 0, + "OwnerCount": 1, + "Sequence": 1027779 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "33442CE111B258424548888D8999F6D064A0866B1300C44AB72E1C5A09765D9D", + "PreviousFields": { + "Balance": "20000000", + "OwnerCount": 0, + "Sequence": 1027778 + }, + "PreviousTxnID": "7C9ACA230488547B4F39EBCE332447FB90AE59B64C1B03BBF474B509B43739EC", + "PreviousTxnLgrSeq": 1027778 + } + }, + { + "CreatedNode": { + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "439684B06C22596B5B86D2F50903B6AA6F68BD07BED636FC6325704B09DE5D61", + "NewFields": { + "Owner": "rwmUSzi5Xp31AjMTEdbvxgWqLETcVNU6Fv", + "RootIndex": "439684B06C22596B5B86D2F50903B6AA6F68BD07BED636FC6325704B09DE5D61" + } + } + }, + { + "CreatedNode": { + "LedgerEntryType": "XChainOwnedClaimID", + "LedgerIndex": "8097863E1200B0174006541763AA8F604782DA10C1BD37190D753C699D69C678", + "NewFields": { + "Account": "rwmUSzi5Xp31AjMTEdbvxgWqLETcVNU6Fv", + "OtherChainSource": "rBXdfZ7NVpdjRfYajPMpviGgq7HLDeuBdR", + "SignatureReward": "100", + "XChainBridge": { + "IssuingChainDoor": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "IssuingChainIssue": { + "currency": "XRP" + }, + "LockingChainDoor": "rMAXACCrp3Y8PpswXcg3bKggHX76V3F8M4", + "LockingChainIssue": { + "currency": "XRP" + } + }, + "XChainClaimID": "ac" + } + } + } + ], + "TransactionIndex": 0, + "TransactionResult": "tesSUCCESS" + }, + "hash": "A42C4E7F5BAF8A9BEB56853114EE686D554F15F400B8DA885A344B13C32D07BC", + "ledger_index": 1027780, + "date": 1695324182000 +} diff --git a/packages/xrpl/test/models/XChainAccountCreateCommit.test.ts b/packages/xrpl/test/models/XChainAccountCreateCommit.test.ts new file mode 100644 index 0000000000..454458fdae --- /dev/null +++ b/packages/xrpl/test/models/XChainAccountCreateCommit.test.ts @@ -0,0 +1,161 @@ +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validateXChainAccountCreateCommit } from '../../src/models/transactions/XChainAccountCreateCommit' + +/** + * XChainAccountCreateCommit Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('XChainAccountCreateCommit', function () { + let tx + + beforeEach(function () { + tx = { + Account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + XChainBridge: { + LockingChainDoor: 'rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL', + LockingChainIssue: { + currency: 'XRP', + }, + IssuingChainDoor: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV', + IssuingChainIssue: { + currency: 'XRP', + }, + }, + Amount: '1000000', + Fee: '10', + Flags: 2147483648, + Destination: 'rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL', + Sequence: 1, + SignatureReward: '10000', + TransactionType: 'XChainAccountCreateCommit', + } as any + }) + + it('verifies valid XChainAccountCreateCommit', function () { + assert.doesNotThrow(() => validateXChainAccountCreateCommit(tx)) + assert.doesNotThrow(() => validate(tx)) + }) + + it('throws w/ missing XChainBridge', function () { + delete tx.XChainBridge + + assert.throws( + () => validateXChainAccountCreateCommit(tx), + ValidationError, + 'XChainAccountCreateCommit: missing field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAccountCreateCommit: missing field XChainBridge', + ) + }) + + it('throws w/ invalid XChainBridge', function () { + tx.XChainBridge = { XChainDoor: 'test' } + + assert.throws( + () => validateXChainAccountCreateCommit(tx), + ValidationError, + 'XChainAccountCreateCommit: invalid field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAccountCreateCommit: invalid field XChainBridge', + ) + }) + + it('throws w/ missing SignatureReward', function () { + delete tx.SignatureReward + + assert.throws( + () => validateXChainAccountCreateCommit(tx), + ValidationError, + 'XChainAccountCreateCommit: missing field SignatureReward', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAccountCreateCommit: missing field SignatureReward', + ) + }) + + it('throws w/ invalid SignatureReward', function () { + tx.SignatureReward = { currency: 'ETH' } + + assert.throws( + () => validateXChainAccountCreateCommit(tx), + ValidationError, + 'XChainAccountCreateCommit: invalid field SignatureReward', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAccountCreateCommit: invalid field SignatureReward', + ) + }) + + it('throws w/ missing Destination', function () { + delete tx.Destination + + assert.throws( + () => validateXChainAccountCreateCommit(tx), + ValidationError, + 'XChainAccountCreateCommit: missing field Destination', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAccountCreateCommit: missing field Destination', + ) + }) + + it('throws w/ invalid Destination', function () { + tx.Destination = 123 + + assert.throws( + () => validateXChainAccountCreateCommit(tx), + ValidationError, + 'XChainAccountCreateCommit: invalid field Destination', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAccountCreateCommit: invalid field Destination', + ) + }) + + it('throws w/ missing Amount', function () { + delete tx.Amount + + assert.throws( + () => validateXChainAccountCreateCommit(tx), + ValidationError, + 'XChainAccountCreateCommit: missing field Amount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAccountCreateCommit: missing field Amount', + ) + }) + + it('throws w/ invalid Amount', function () { + tx.Amount = { currency: 'ETH' } + + assert.throws( + () => validateXChainAccountCreateCommit(tx), + ValidationError, + 'XChainAccountCreateCommit: invalid field Amount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAccountCreateCommit: invalid field Amount', + ) + }) +}) diff --git a/packages/xrpl/test/models/XChainAddAccountCreateAttestation.test.ts b/packages/xrpl/test/models/XChainAddAccountCreateAttestation.test.ts new file mode 100644 index 0000000000..4fb98fd584 --- /dev/null +++ b/packages/xrpl/test/models/XChainAddAccountCreateAttestation.test.ts @@ -0,0 +1,381 @@ +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validateXChainAddAccountCreateAttestation } from '../../src/models/transactions/XChainAddAccountCreateAttestation' + +/** + * XChainAddAccountCreateAttestation Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('XChainAddAccountCreateAttestation', function () { + let tx + + beforeEach(function () { + tx = { + Account: 'r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT', + Amount: '10000000', + AttestationRewardAccount: 'r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT', + AttestationSignerAccount: 'r9cYxdjQsoXAEz3qQJc961SNLaXRkWXCvT', + Destination: 'rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi', + Fee: '20', + LastLedgerSequence: 13, + OtherChainSource: 'raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym', + PublicKey: + 'ED1F4A024ACFEBDB6C7AA88DEDE3364E060487EA31B14CC9E0D610D152B31AADC2', + Sequence: 5, + Signature: + 'EEFCFA3DC2AB4AB7C4D2EBBC168CB621A11B82BABD86534DFC8EFA72439A496' + + '62D744073CD848E7A587A95B35162CDF9A69BB237E72C9537A987F5B8C394F30D', + SignatureReward: '100', + TransactionType: 'XChainAddAccountCreateAttestation', + WasLockingChainSend: 1, + XChainAccountCreateCount: '0000000000000006', + XChainBridge: { + IssuingChainDoor: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + IssuingChainIssue: { + currency: 'XRP', + }, + LockingChainDoor: 'rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg', + LockingChainIssue: { + currency: 'XRP', + }, + }, + } as any + }) + + it('verifies valid XChainAddAccountCreateAttestation', function () { + assert.doesNotThrow(() => validateXChainAddAccountCreateAttestation(tx)) + assert.doesNotThrow(() => validate(tx)) + }) + + it('throws w/ missing Amount', function () { + delete tx.Amount + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field Amount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field Amount', + ) + }) + + it('throws w/ invalid Amount', function () { + tx.Amount = { currency: 'ETH' } + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field Amount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field Amount', + ) + }) + + it('throws w/ missing AttestationRewardAccount', function () { + delete tx.AttestationRewardAccount + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field AttestationRewardAccount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field AttestationRewardAccount', + ) + }) + + it('throws w/ invalid AttestationRewardAccount', function () { + tx.AttestationRewardAccount = 123 + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field AttestationRewardAccount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field AttestationRewardAccount', + ) + }) + + it('throws w/ missing AttestationSignerAccount', function () { + delete tx.AttestationSignerAccount + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field AttestationSignerAccount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field AttestationSignerAccount', + ) + }) + + it('throws w/ invalid AttestationSignerAccount', function () { + tx.AttestationSignerAccount = 123 + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field AttestationSignerAccount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field AttestationSignerAccount', + ) + }) + + it('throws w/ missing Destination', function () { + delete tx.Destination + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field Destination', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field Destination', + ) + }) + + it('throws w/ invalid Destination', function () { + tx.Destination = 123 + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field Destination', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field Destination', + ) + }) + + it('throws w/ missing OtherChainSource', function () { + delete tx.OtherChainSource + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field OtherChainSource', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field OtherChainSource', + ) + }) + + it('throws w/ invalid OtherChainSource', function () { + tx.OtherChainSource = 123 + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field OtherChainSource', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field OtherChainSource', + ) + }) + + it('throws w/ missing PublicKey', function () { + delete tx.PublicKey + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field PublicKey', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field PublicKey', + ) + }) + + it('throws w/ invalid PublicKey', function () { + tx.PublicKey = 123 + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field PublicKey', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field PublicKey', + ) + }) + + it('throws w/ missing Signature', function () { + delete tx.Signature + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field Signature', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field Signature', + ) + }) + + it('throws w/ invalid Signature', function () { + tx.Signature = 123 + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field Signature', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field Signature', + ) + }) + + it('throws w/ missing SignatureReward', function () { + delete tx.SignatureReward + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field SignatureReward', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field SignatureReward', + ) + }) + + it('throws w/ invalid SignatureReward', function () { + tx.SignatureReward = { currency: 'ETH' } + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field SignatureReward', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field SignatureReward', + ) + }) + + it('throws w/ missing WasLockingChainSend', function () { + delete tx.WasLockingChainSend + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field WasLockingChainSend', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field WasLockingChainSend', + ) + }) + + it('throws w/ invalid WasLockingChainSend', function () { + tx.WasLockingChainSend = 2 + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field WasLockingChainSend', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field WasLockingChainSend', + ) + }) + + it('throws w/ missing XChainAccountCreateCount', function () { + delete tx.XChainAccountCreateCount + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field XChainAccountCreateCount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field XChainAccountCreateCount', + ) + }) + + it('throws w/ invalid XChainAccountCreateCount', function () { + tx.XChainAccountCreateCount = { currency: 'ETH' } + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field XChainAccountCreateCount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field XChainAccountCreateCount', + ) + }) + + it('throws w/ missing XChainBridge', function () { + delete tx.XChainBridge + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: missing field XChainBridge', + ) + }) + + it('throws w/ invalid XChainBridge', function () { + tx.XChainBridge = { XChainDoor: 'test' } + + assert.throws( + () => validateXChainAddAccountCreateAttestation(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddAccountCreateAttestation: invalid field XChainBridge', + ) + }) +}) diff --git a/packages/xrpl/test/models/XChainAddClaimAttestation.test.ts b/packages/xrpl/test/models/XChainAddClaimAttestation.test.ts new file mode 100644 index 0000000000..4e7d1aeb7a --- /dev/null +++ b/packages/xrpl/test/models/XChainAddClaimAttestation.test.ts @@ -0,0 +1,334 @@ +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validateXChainAddClaimAttestation } from '../../src/models/transactions/XChainAddClaimAttestation' + +/** + * XChainAddClaimAttestation Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('XChainAddClaimAttestation', function () { + let tx + + beforeEach(function () { + tx = { + Account: 'rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3', + Amount: '10000000', + AttestationRewardAccount: 'rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3', + AttestationSignerAccount: 'rsqvD8WFFEBBv4nztpoW9YYXJ7eRzLrtc3', + Destination: 'rJdTJRJZ6GXCCRaamHJgEqVzB7Zy4557Pi', + Fee: '20', + LastLedgerSequence: 19, + OtherChainSource: 'raFcdz1g8LWJDJWJE2ZKLRGdmUmsTyxaym', + PublicKey: + 'ED7541DEC700470F54276C90C333A13CDBB5D341FD43C60CEA12170F6D6D4E1136', + Sequence: 9, + Signature: + '7C175050B08000AD35EEB2D87E16CD3F95A0AEEBF2A049474275153D9D4DD44528FE99AA50E71660A15B0B768E1B90E609BBD5DC7AFAFD45D9705D72D40EA10C', + TransactionType: 'XChainAddClaimAttestation', + WasLockingChainSend: 1, + XChainBridge: { + IssuingChainDoor: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + IssuingChainIssue: { + currency: 'XRP', + }, + LockingChainDoor: 'rDJVtEuDKr4rj1B3qtW7R5TVWdXV2DY7Qg', + LockingChainIssue: { + currency: 'XRP', + }, + }, + XChainClaimID: '0000000000000001', + } as any + }) + + it('verifies valid XChainAddClaimAttestation', function () { + assert.doesNotThrow(() => validateXChainAddClaimAttestation(tx)) + assert.doesNotThrow(() => validate(tx)) + }) + + it('throws w/ missing Amount', function () { + delete tx.Amount + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field Amount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field Amount', + ) + }) + + it('throws w/ invalid Amount', function () { + tx.Amount = { currency: 'ETH' } + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field Amount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field Amount', + ) + }) + + it('throws w/ missing AttestationRewardAccount', function () { + delete tx.AttestationRewardAccount + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field AttestationRewardAccount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field AttestationRewardAccount', + ) + }) + + it('throws w/ invalid AttestationRewardAccount', function () { + tx.AttestationRewardAccount = 123 + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field AttestationRewardAccount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field AttestationRewardAccount', + ) + }) + + it('throws w/ missing AttestationSignerAccount', function () { + delete tx.AttestationSignerAccount + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field AttestationSignerAccount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field AttestationSignerAccount', + ) + }) + + it('throws w/ invalid AttestationSignerAccount', function () { + tx.AttestationSignerAccount = 123 + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field AttestationSignerAccount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field AttestationSignerAccount', + ) + }) + + it('throws w/ invalid Destination', function () { + tx.Destination = 123 + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field Destination', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field Destination', + ) + }) + + it('throws w/ missing OtherChainSource', function () { + delete tx.OtherChainSource + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field OtherChainSource', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field OtherChainSource', + ) + }) + + it('throws w/ invalid OtherChainSource', function () { + tx.OtherChainSource = 123 + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field OtherChainSource', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field OtherChainSource', + ) + }) + + it('throws w/ missing PublicKey', function () { + delete tx.PublicKey + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field PublicKey', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field PublicKey', + ) + }) + + it('throws w/ invalid PublicKey', function () { + tx.PublicKey = 123 + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field PublicKey', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field PublicKey', + ) + }) + + it('throws w/ missing Signature', function () { + delete tx.Signature + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field Signature', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field Signature', + ) + }) + + it('throws w/ invalid Signature', function () { + tx.Signature = 123 + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field Signature', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field Signature', + ) + }) + + it('throws w/ missing WasLockingChainSend', function () { + delete tx.WasLockingChainSend + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field WasLockingChainSend', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field WasLockingChainSend', + ) + }) + + it('throws w/ invalid WasLockingChainSend', function () { + tx.WasLockingChainSend = 2 + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field WasLockingChainSend', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field WasLockingChainSend', + ) + }) + + it('throws w/ missing XChainBridge', function () { + delete tx.XChainBridge + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field XChainBridge', + ) + }) + + it('throws w/ invalid XChainBridge', function () { + tx.XChainBridge = { XChainDoor: 'test' } + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field XChainBridge', + ) + }) + + it('throws w/ missing XChainClaimID', function () { + delete tx.XChainClaimID + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field XChainClaimID', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: missing field XChainClaimID', + ) + }) + + it('throws w/ invalid XChainClaimID', function () { + tx.XChainClaimID = { currency: 'ETH' } + + assert.throws( + () => validateXChainAddClaimAttestation(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field XChainClaimID', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainAddClaimAttestation: invalid field XChainClaimID', + ) + }) +}) diff --git a/packages/xrpl/test/models/XChainClaim.test.ts b/packages/xrpl/test/models/XChainClaim.test.ts new file mode 100644 index 0000000000..7b854ea786 --- /dev/null +++ b/packages/xrpl/test/models/XChainClaim.test.ts @@ -0,0 +1,176 @@ +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validateXChainClaim } from '../../src/models/transactions/XChainClaim' + +/** + * XChainClaim Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('XChainClaim', function () { + let tx + + beforeEach(function () { + tx = { + Account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + Amount: '10000', + XChainBridge: { + LockingChainDoor: 'rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL', + LockingChainIssue: { + currency: 'XRP', + }, + IssuingChainDoor: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV', + IssuingChainIssue: { + currency: 'XRP', + }, + }, + Destination: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV', + Fee: '10', + Flags: 2147483648, + Sequence: 1, + TransactionType: 'XChainClaim', + XChainClaimID: '0000000000000001', + } as any + }) + + it('verifies valid XChainClaim', function () { + assert.doesNotThrow(() => validateXChainClaim(tx)) + assert.doesNotThrow(() => validate(tx)) + }) + + it('throws w/ missing XChainBridge', function () { + delete tx.XChainBridge + + assert.throws( + () => validateXChainClaim(tx), + ValidationError, + 'XChainClaim: missing field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainClaim: missing field XChainBridge', + ) + }) + + it('throws w/ invalid XChainBridge', function () { + tx.XChainBridge = { XChainDoor: 'test' } + + assert.throws( + () => validateXChainClaim(tx), + ValidationError, + 'XChainClaim: invalid field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainClaim: invalid field XChainBridge', + ) + }) + + it('throws w/ missing XChainClaimID', function () { + delete tx.XChainClaimID + + assert.throws( + () => validateXChainClaim(tx), + ValidationError, + 'XChainClaim: missing field XChainClaimID', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainClaim: missing field XChainClaimID', + ) + }) + + it('throws w/ invalid XChainClaimID', function () { + tx.XChainClaimID = { currency: 'ETH' } + + assert.throws( + () => validateXChainClaim(tx), + ValidationError, + 'XChainClaim: invalid field XChainClaimID', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainClaim: invalid field XChainClaimID', + ) + }) + + it('throws w/ missing Destination', function () { + delete tx.Destination + + assert.throws( + () => validateXChainClaim(tx), + ValidationError, + 'XChainClaim: missing field Destination', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainClaim: missing field Destination', + ) + }) + + it('throws w/ invalid Destination', function () { + tx.Destination = 123 + + assert.throws( + () => validateXChainClaim(tx), + ValidationError, + 'XChainClaim: invalid field Destination', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainClaim: invalid field Destination', + ) + }) + + it('throws w/ invalid DestinationTag', function () { + tx.DestinationTag = 'number' + + assert.throws( + () => validateXChainClaim(tx), + ValidationError, + 'XChainClaim: invalid field DestinationTag', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainClaim: invalid field DestinationTag', + ) + }) + + it('throws w/ missing Amount', function () { + delete tx.Amount + + assert.throws( + () => validateXChainClaim(tx), + ValidationError, + 'XChainClaim: missing field Amount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainClaim: missing field Amount', + ) + }) + + it('throws w/ invalid Amount', function () { + tx.Amount = { currency: 'ETH' } + + assert.throws( + () => validateXChainClaim(tx), + ValidationError, + 'XChainClaim: invalid field Amount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainClaim: invalid field Amount', + ) + }) +}) diff --git a/packages/xrpl/test/models/XChainCommit.test.ts b/packages/xrpl/test/models/XChainCommit.test.ts new file mode 100644 index 0000000000..4963cf1bef --- /dev/null +++ b/packages/xrpl/test/models/XChainCommit.test.ts @@ -0,0 +1,145 @@ +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validateXChainCommit } from '../../src/models/transactions/XChainCommit' + +/** + * XChainCommit Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('XChainCommit', function () { + let tx + + beforeEach(function () { + tx = { + Account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + Amount: '10000', + XChainBridge: { + LockingChainDoor: 'rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL', + LockingChainIssue: { + currency: 'XRP', + }, + IssuingChainDoor: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV', + IssuingChainIssue: { + currency: 'XRP', + }, + }, + Fee: '10', + Flags: 2147483648, + Sequence: 1, + TransactionType: 'XChainCommit', + XChainClaimID: '0000000000000001', + } as any + }) + + it('verifies valid XChainCommit', function () { + assert.doesNotThrow(() => validateXChainCommit(tx)) + assert.doesNotThrow(() => validate(tx)) + }) + + it('throws w/ missing XChainBridge', function () { + delete tx.XChainBridge + + assert.throws( + () => validateXChainCommit(tx), + ValidationError, + 'XChainCommit: missing field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCommit: missing field XChainBridge', + ) + }) + + it('throws w/ invalid XChainBridge', function () { + tx.XChainBridge = { XChainDoor: 'test' } + + assert.throws( + () => validateXChainCommit(tx), + ValidationError, + 'XChainCommit: invalid field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCommit: invalid field XChainBridge', + ) + }) + + it('throws w/ missing XChainClaimID', function () { + delete tx.XChainClaimID + + assert.throws( + () => validateXChainCommit(tx), + ValidationError, + 'XChainCommit: missing field XChainClaimID', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCommit: missing field XChainClaimID', + ) + }) + + it('throws w/ invalid XChainClaimID', function () { + tx.XChainClaimID = { currency: 'ETH' } + + assert.throws( + () => validateXChainCommit(tx), + ValidationError, + 'XChainCommit: invalid field XChainClaimID', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCommit: invalid field XChainClaimID', + ) + }) + + it('throws w/ invalid OtherChainDestination', function () { + tx.OtherChainDestination = 123 + + assert.throws( + () => validateXChainCommit(tx), + ValidationError, + 'XChainCommit: invalid field OtherChainDestination', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCommit: invalid field OtherChainDestination', + ) + }) + + it('throws w/ missing Amount', function () { + delete tx.Amount + + assert.throws( + () => validateXChainCommit(tx), + ValidationError, + 'XChainCommit: missing field Amount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCommit: missing field Amount', + ) + }) + + it('throws w/ invalid Amount', function () { + tx.Amount = { currency: 'ETH' } + + assert.throws( + () => validateXChainCommit(tx), + ValidationError, + 'XChainCommit: invalid field Amount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCommit: invalid field Amount', + ) + }) +}) diff --git a/packages/xrpl/test/models/XChainCreateBridge.test.ts b/packages/xrpl/test/models/XChainCreateBridge.test.ts new file mode 100644 index 0000000000..dfd174058e --- /dev/null +++ b/packages/xrpl/test/models/XChainCreateBridge.test.ts @@ -0,0 +1,115 @@ +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validateXChainCreateBridge } from '../../src/models/transactions/XChainCreateBridge' + +/** + * XChainCreateBridge Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('XChainCreateBridge', function () { + let tx + + beforeEach(function () { + tx = { + Account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + XChainBridge: { + LockingChainDoor: 'rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL', + LockingChainIssue: { + currency: 'XRP', + }, + IssuingChainDoor: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV', + IssuingChainIssue: { + currency: 'XRP', + }, + }, + Fee: '10', + Flags: 0, + MinAccountCreateAmount: '10000', + Sequence: 1, + SignatureReward: '1000', + TransactionType: 'XChainCreateBridge', + } as any + }) + + it('verifies valid XChainCreateBridge', function () { + assert.doesNotThrow(() => validateXChainCreateBridge(tx)) + assert.doesNotThrow(() => validate(tx)) + }) + + it('throws w/ missing XChainBridge', function () { + delete tx.XChainBridge + + assert.throws( + () => validateXChainCreateBridge(tx), + ValidationError, + 'XChainCreateBridge: missing field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCreateBridge: missing field XChainBridge', + ) + }) + + it('throws w/ invalid XChainBridge', function () { + tx.XChainBridge = { XChainDoor: 'test' } + + assert.throws( + () => validateXChainCreateBridge(tx), + ValidationError, + 'XChainCreateBridge: invalid field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCreateBridge: invalid field XChainBridge', + ) + }) + + it('throws w/ missing SignatureReward', function () { + delete tx.SignatureReward + + assert.throws( + () => validateXChainCreateBridge(tx), + ValidationError, + 'XChainCreateBridge: missing field SignatureReward', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCreateBridge: missing field SignatureReward', + ) + }) + + it('throws w/ invalid SignatureReward', function () { + tx.SignatureReward = { currency: 'ETH' } + + assert.throws( + () => validateXChainCreateBridge(tx), + ValidationError, + 'XChainCreateBridge: invalid field SignatureReward', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCreateBridge: invalid field SignatureReward', + ) + }) + + it('throws w/ invalid MinAccountCreateAmount', function () { + tx.MinAccountCreateAmount = { currency: 'ETH' } + + assert.throws( + () => validateXChainCreateBridge(tx), + ValidationError, + 'XChainCreateBridge: invalid field MinAccountCreateAmount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCreateBridge: invalid field MinAccountCreateAmount', + ) + }) +}) diff --git a/packages/xrpl/test/models/XChainCreateClaimID.test.ts b/packages/xrpl/test/models/XChainCreateClaimID.test.ts new file mode 100644 index 0000000000..503fae0130 --- /dev/null +++ b/packages/xrpl/test/models/XChainCreateClaimID.test.ts @@ -0,0 +1,130 @@ +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validateXChainCreateClaimID } from '../../src/models/transactions/XChainCreateClaimID' + +/** + * XChainCreateClaimID Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('XChainCreateClaimID', function () { + let tx + + beforeEach(function () { + tx = { + Account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + XChainBridge: { + LockingChainDoor: 'rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL', + LockingChainIssue: { + currency: 'XRP', + }, + IssuingChainDoor: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV', + IssuingChainIssue: { + currency: 'XRP', + }, + }, + Fee: '10', + Flags: 2147483648, + OtherChainSource: 'rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL', + Sequence: 1, + SignatureReward: '10000', + TransactionType: 'XChainCreateClaimID', + } as any + }) + + it('verifies valid XChainCreateClaimID', function () { + assert.doesNotThrow(() => validateXChainCreateClaimID(tx)) + assert.doesNotThrow(() => validate(tx)) + }) + + it('throws w/ missing XChainBridge', function () { + delete tx.XChainBridge + + assert.throws( + () => validateXChainCreateClaimID(tx), + ValidationError, + 'XChainCreateClaimID: missing field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCreateClaimID: missing field XChainBridge', + ) + }) + + it('throws w/ invalid XChainBridge', function () { + tx.XChainBridge = { XChainDoor: 'test' } + + assert.throws( + () => validateXChainCreateClaimID(tx), + ValidationError, + 'XChainCreateClaimID: invalid field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCreateClaimID: invalid field XChainBridge', + ) + }) + + it('throws w/ missing SignatureReward', function () { + delete tx.SignatureReward + + assert.throws( + () => validateXChainCreateClaimID(tx), + ValidationError, + 'XChainCreateClaimID: missing field SignatureReward', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCreateClaimID: missing field SignatureReward', + ) + }) + + it('throws w/ invalid SignatureReward', function () { + tx.SignatureReward = { currency: 'ETH' } + + assert.throws( + () => validateXChainCreateClaimID(tx), + ValidationError, + 'XChainCreateClaimID: invalid field SignatureReward', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCreateClaimID: invalid field SignatureReward', + ) + }) + + it('throws w/ missing OtherChainSource', function () { + delete tx.OtherChainSource + + assert.throws( + () => validateXChainCreateClaimID(tx), + ValidationError, + 'XChainCreateClaimID: missing field OtherChainSource', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCreateClaimID: missing field OtherChainSource', + ) + }) + + it('throws w/ invalid OtherChainSource', function () { + tx.OtherChainSource = 123 + + assert.throws( + () => validateXChainCreateClaimID(tx), + ValidationError, + 'XChainCreateClaimID: invalid field OtherChainSource', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainCreateClaimID: invalid field OtherChainSource', + ) + }) +}) diff --git a/packages/xrpl/test/models/XChainModifyBridge.test.ts b/packages/xrpl/test/models/XChainModifyBridge.test.ts new file mode 100644 index 0000000000..57eeb50823 --- /dev/null +++ b/packages/xrpl/test/models/XChainModifyBridge.test.ts @@ -0,0 +1,100 @@ +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validateXChainModifyBridge } from '../../src/models/transactions/XChainModifyBridge' + +/** + * XChainModifyBridge Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('XChainModifyBridge', function () { + let tx + + beforeEach(function () { + tx = { + Account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + XChainBridge: { + LockingChainDoor: 'rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL', + LockingChainIssue: { + currency: 'XRP', + }, + IssuingChainDoor: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV', + IssuingChainIssue: { + currency: 'XRP', + }, + }, + Fee: '10', + Flags: 0, + MinAccountCreateAmount: '10000', + Sequence: 1, + SignatureReward: '1000', + TransactionType: 'XChainModifyBridge', + } as any + }) + + it('verifies valid XChainModifyBridge', function () { + assert.doesNotThrow(() => validateXChainModifyBridge(tx)) + assert.doesNotThrow(() => validate(tx)) + }) + + it('throws w/ missing XChainBridge', function () { + delete tx.XChainBridge + + assert.throws( + () => validateXChainModifyBridge(tx), + ValidationError, + 'XChainModifyBridge: missing field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainModifyBridge: missing field XChainBridge', + ) + }) + + it('throws w/ invalid XChainBridge', function () { + tx.XChainBridge = { XChainDoor: 'test' } + + assert.throws( + () => validateXChainModifyBridge(tx), + ValidationError, + 'XChainModifyBridge: invalid field XChainBridge', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainModifyBridge: invalid field XChainBridge', + ) + }) + + it('throws w/ invalid SignatureReward', function () { + tx.SignatureReward = { currency: 'ETH' } + + assert.throws( + () => validateXChainModifyBridge(tx), + ValidationError, + 'XChainModifyBridge: invalid field SignatureReward', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainModifyBridge: invalid field SignatureReward', + ) + }) + + it('throws w/ invalid MinAccountCreateAmount', function () { + tx.MinAccountCreateAmount = { currency: 'ETH' } + + assert.throws( + () => validateXChainModifyBridge(tx), + ValidationError, + 'XChainModifyBridge: invalid field MinAccountCreateAmount', + ) + assert.throws( + () => validate(tx), + ValidationError, + 'XChainModifyBridge: invalid field MinAccountCreateAmount', + ) + }) +}) diff --git a/packages/xrpl/test/models/baseTransaction.test.ts b/packages/xrpl/test/models/baseTransaction.test.ts index e3aa7af710..10e272f7f2 100644 --- a/packages/xrpl/test/models/baseTransaction.test.ts +++ b/packages/xrpl/test/models/baseTransaction.test.ts @@ -78,7 +78,7 @@ describe('BaseTransaction', function () { assert.throws( () => validateBaseTransaction(invalidFee), ValidationError, - 'BaseTransaction: invalid Fee', + 'Payment: invalid field Fee', ) }) @@ -92,7 +92,7 @@ describe('BaseTransaction', function () { assert.throws( () => validateBaseTransaction(invalidSeq), ValidationError, - 'BaseTransaction: invalid Sequence', + 'Payment: invalid field Sequence', ) }) @@ -106,7 +106,7 @@ describe('BaseTransaction', function () { assert.throws( () => validateBaseTransaction(invalidID), ValidationError, - 'BaseTransaction: invalid AccountTxnID', + 'Payment: invalid field AccountTxnID', ) }) @@ -120,7 +120,7 @@ describe('BaseTransaction', function () { assert.throws( () => validateBaseTransaction(invalidLastLedgerSequence), ValidationError, - 'BaseTransaction: invalid LastLedgerSequence', + 'Payment: invalid field LastLedgerSequence', ) }) @@ -134,7 +134,7 @@ describe('BaseTransaction', function () { assert.throws( () => validateBaseTransaction(invalidSourceTag), ValidationError, - 'BaseTransaction: invalid SourceTag', + 'Payment: invalid field SourceTag', ) }) @@ -148,7 +148,7 @@ describe('BaseTransaction', function () { assert.throws( () => validateBaseTransaction(invalidSigningPubKey), ValidationError, - 'BaseTransaction: invalid SigningPubKey', + 'Payment: invalid field SigningPubKey', ) }) @@ -162,7 +162,7 @@ describe('BaseTransaction', function () { assert.throws( () => validateBaseTransaction(invalidTicketSequence), ValidationError, - 'BaseTransaction: invalid TicketSequence', + 'Payment: invalid field TicketSequence', ) }) @@ -176,7 +176,7 @@ describe('BaseTransaction', function () { assert.throws( () => validateBaseTransaction(invalidTxnSignature), ValidationError, - 'BaseTransaction: invalid TxnSignature', + 'Payment: invalid field TxnSignature', ) }) @@ -242,7 +242,7 @@ describe('BaseTransaction', function () { assert.throws( () => validateBaseTransaction(invalidNetworkID), ValidationError, - 'BaseTransaction: invalid NetworkID', + 'Payment: invalid field NetworkID', ) }) }) diff --git a/packages/xrpl/test/utils/getNFTokenID.test.ts b/packages/xrpl/test/utils/getNFTokenID.test.ts index 5064b81aed..6720fb46ef 100644 --- a/packages/xrpl/test/utils/getNFTokenID.test.ts +++ b/packages/xrpl/test/utils/getNFTokenID.test.ts @@ -1,19 +1,18 @@ import { assert } from 'chai' import { getNFTokenID } from '../../src' -import * as NFTokenResponse from '../fixtures/rippled/mintNFTMeta.json' -import * as NFTokenResponse2 from '../fixtures/rippled/mintNFTMeta2.json' +import fixtures from '../fixtures/rippled' describe('getNFTokenID', function () { it('decode a valid NFTokenID', function () { - const result = getNFTokenID(NFTokenResponse.meta) + const result = getNFTokenID(fixtures.tx.NFTokenMint.meta) const expectedNFTokenID = '00081388DC1AB4937C899037B2FDFC3CB20F6F64E73120BB5F8AA66A00000228' assert.equal(result, expectedNFTokenID) }) it('decode a different valid NFTokenID', function () { - const result = getNFTokenID(NFTokenResponse2.meta) + const result = getNFTokenID(fixtures.tx.NFTokenMint2.meta) const expectedNFTokenID = '0008125CBE4B401B2F62ED35CC67362165AA813CCA06316FFA766254000003EE' assert.equal(result, expectedNFTokenID) @@ -21,8 +20,8 @@ describe('getNFTokenID', function () { it('fails with nice error when given raw response instead of meta', function () { assert.throws(() => { - // @ts-expect-error - Validating error for javascript users - const _ = getNFTokenID(NFTokenResponse) + // @ts-expect-error -- on purpose, to check the error + const _ = getNFTokenID(fixtures.tx.NFTokenMint) }, /^Unable to parse the parameter given to getNFTokenID.*/u) }) }) diff --git a/packages/xrpl/test/utils/getXChainClaimID.test.ts b/packages/xrpl/test/utils/getXChainClaimID.test.ts new file mode 100644 index 0000000000..fba28528eb --- /dev/null +++ b/packages/xrpl/test/utils/getXChainClaimID.test.ts @@ -0,0 +1,25 @@ +import { assert } from 'chai' + +import { getXChainClaimID } from '../../src' +import fixtures from '../fixtures/rippled' + +describe('getXChainClaimID', function () { + it('decode a valid XChainClaimID', function () { + const result = getXChainClaimID(fixtures.tx.XChainCreateClaimID.meta) + const expectedXChainClaimID = 'b0' + assert.equal(result, expectedXChainClaimID) + }) + + it('decode a different valid XChainClaimID', function () { + const result = getXChainClaimID(fixtures.tx.XChainCreateClaimID2.meta) + const expectedXChainClaimID = 'ac' + assert.equal(result, expectedXChainClaimID) + }) + + it('fails with nice error when given raw response instead of meta', function () { + assert.throws(() => { + // @ts-expect-error -- on purpose, to check the error + const _ = getXChainClaimID(fixtures.tx.XChainCreateClaimID) + }, /^Unable to parse the parameter given to getXChainClaimID.*/u) + }) +}) diff --git a/packages/xrpl/tools/createValidate.js b/packages/xrpl/tools/createValidate.js new file mode 100644 index 0000000000..05c1499a1e --- /dev/null +++ b/packages/xrpl/tools/createValidate.js @@ -0,0 +1,147 @@ +/* eslint-disable no-continue -- unneeded here */ +/** + * This file writes the `validate` function for a transaction, when provided the model name in the `src/models/transactions` + * folder. + */ +const fs = require('fs') + +const NORMAL_TYPES = ['number', 'string'] +const NUMBERS = ['0', '1'] + +// TODO: rewrite this to use regex + +async function main() { + if (process.argv.length < 3) { + console.log(`Usage: ${process.argv[0]} ${process.argv[1]} TxName`) + process.exit(1) + } + const modelName = process.argv[2] + const filename = `./src/models/transactions/${modelName}.ts` + const [model, txName] = await getModel(filename) + return processModel(model, txName) +} + +async function getModel(filename) { + let model = '' + let started = false + let ended = false + const data = await fs.promises.readFile(filename, { encoding: 'utf8' }) + const lines = data.split('\n') + for (const line of lines) { + if (ended) { + continue + } + if (!started && !line.startsWith('export')) { + continue + } + if (!started && line.includes('Flags')) { + continue + } + if (!started) { + started = true + } + model += `${line}\n` + if (line === '}') { + ended = true + } + } + const name_line = model.split('\n')[0].split(' ') + const txName = name_line[2] + return [model, txName] +} + +function getValidationFunction(paramType) { + if (NORMAL_TYPES.includes(paramType)) { + const paramTypeCapitalized = + paramType.substring(0, 1).toUpperCase() + paramType.substring(1) + return `is${paramTypeCapitalized}(inp)` + } + if (NUMBERS.includes(paramType)) { + return `inp === ${paramType}` + } + return `is${paramType}(inp)` +} + +function getValidationLine(validationFns) { + if (validationFns.length === 1) { + if (!validationFns[0].includes('===')) { + // Example: `validateRequiredFields(tx, 'Amount', isAmount)` + const validationFn = validationFns[0] + // strip the `(inp)` in e.g. `isAmount(inp)` + return validationFn.substring(0, validationFn.length - 5) + } + } + // Example: + // `validateRequiredFields(tx, 'XChainAccountCreateCount', + // (inp) => isNumber(inp) || isString(inp)))` + return `(inp) => ${validationFns.join(' || ')}` +} + +function processModel(model, txName) { + let output = '' + // process the TS model and get the types of each parameter + for (let line of model.split('\n')) { + if (line === '') { + continue + } + if (line.startsWith('export')) { + continue + } + if (line === '}') { + continue + } + line = line.trim() + if (line.startsWith('TransactionType')) { + continue + } + if (line.startsWith('Flags')) { + continue + } + if (line.startsWith('/**')) { + continue + } + if (line.startsWith('*')) { + continue + } + + // process the line with a type + const split = line.split(' ') + const param = split[0].replace('?:', '').replace(':', '').trim() + const paramTypes = split.slice(1) + const optional = split[0].endsWith('?:') + const functionName = optional + ? 'validateOptionalField' + : 'validateRequiredField' + + // process the types and turn them into a validation function + let idx = 0 + const if_outputs = [] + while (idx < paramTypes.length) { + const paramType = paramTypes[idx] + if_outputs.push(getValidationFunction(paramType)) + idx += 2 + } + + output += ` ${functionName}(tx, '${param}', ${getValidationLine( + if_outputs, + )})\n\n` + } + output = output.substring(0, output.length - 1) + output += '}\n' + + // initial output content + output = `/** + * Verify the form and type of a ${txName} at runtime. + * + * @param tx - A ${txName} Transaction. + * @throws When the ${txName} is malformed. + */ +export function validate${txName}(tx: Record): void { + validateBaseTransaction(tx) + +${output}` + + return output +} + +main().then(console.log) diff --git a/packages/xrpl/tsconfig.eslint.json b/packages/xrpl/tsconfig.eslint.json index 604663f0ab..70b9b96580 100644 --- a/packages/xrpl/tsconfig.eslint.json +++ b/packages/xrpl/tsconfig.eslint.json @@ -6,6 +6,7 @@ "./test/**/*.json", "./src/**/*.json", "./snippets/src/**/*.ts", - ".eslintrc.js" + ".eslintrc.js", ], + "exclude": ["./tools/*.js"] } diff --git a/tools/createValidateTests.js b/tools/createValidateTests.js new file mode 100644 index 0000000000..570644a619 --- /dev/null +++ b/tools/createValidateTests.js @@ -0,0 +1,187 @@ +const fs = require("fs"); +const fixtures = require("../packages/ripple-binary-codec/test/fixtures/codec-fixtures.json"); + +const NORMAL_TYPES = ["number", "string"]; +const NUMBERS = ["0", "1"]; + +function getTx(txName) { + transactions = fixtures.transactions; + const validTxs = fixtures.transactions + .filter((tx) => tx.json.TransactionType === txName) + .map((tx) => tx.json); + const validTx = validTxs[0]; + delete validTx.TxnSignature; + delete validTx.SigningPubKey; + return JSON.stringify(validTx, null, 2); +} + +function main() { + const modelName = process.argv[2]; + const filename = `./packages/xrpl/src/models/transactions/${modelName}.ts`; + const [model, txName] = getModel(filename); + return processModel(model, txName); +} + +// Extract just the model from the file +function getModel(filename) { + let model = ""; + let started = false; + let ended = false; + const data = fs.readFileSync(filename, "utf8"); + const lines = data.split("\n"); + for (let line of lines) { + if (ended) { + continue; + } + if (!started && !line.startsWith("export")) { + continue; + } + if (!started && line.includes("Flags")) { + continue; + } + if (!started) { + started = true; + } + model += line + "\n"; + if (line === "}") { + ended = true; + } + } + const name_line = model.split("\n")[0].split(" "); + const txName = name_line[2]; + return [model, txName]; +} + +function getInvalidValue(paramTypes) { + if (paramTypes.length === 1) { + const paramType = paramTypes[0]; + if (paramType == "number") { + return "'number'"; + } else if (paramType == "string") { + return 123; + } else if (paramType == "IssuedCurrency") { + return JSON.stringify({ test: "test" }); + } else if (paramType == "Amount") { + return JSON.stringify({ currency: "ETH" }); + } else if (paramType == "XChainBridge") { + return JSON.stringify({ XChainDoor: "test" }); + } else { + throw Error(`${paramType} not supported yet`); + } + } + + const simplifiedParamTypes = paramTypes.filter( + (_paramType, index) => index % 2 == 0 + ); + if (JSON.stringify(simplifiedParamTypes) === '["0","1"]') { + return 2; + } else if (JSON.stringify(simplifiedParamTypes) === '["number","string"]') { + return JSON.stringify({ currency: "ETH" }); + } else { + throw Error(`${simplifiedParamTypes} not supported yet`); + } +} + +// Process the model and build the tests + +function processModel(model, txName) { + let output = ""; + for (let line of model.split("\n")) { + if (line == "") { + continue; + } + if (line.startsWith("export")) { + continue; + } + if (line == "}") { + continue; + } + line = line.trim(); + if (line.startsWith("TransactionType")) { + continue; + } + if (line.startsWith("Flags")) { + // TODO: support flag checking + continue; + } + if (line.startsWith("/**")) { + continue; + } + if (line.startsWith("*")) { + continue; + } + + const split = line.split(" "); + const param = split[0].replace("?:", "").replace(":", "").trim(); + const paramTypes = split.slice(1); + const optional = split[0].endsWith("?:"); + + if (!optional) { + output += ` it("throws w/ missing ${param}", function () { + delete tx.${param} + + assert.throws( + () => validate${txName}(tx), + ValidationError, + '${txName}: missing field ${param}', + ) + assert.throws( + () => validate(tx), + ValidationError, + '${txName}: missing field ${param}', + ) + }) + +`; + } + + const fakeValue = getInvalidValue(paramTypes); + output += ` it('throws w/ invalid ${param}', function () { + tx.${param} = ${fakeValue} + + assert.throws( + () => validate${txName}(tx), + ValidationError, + '${txName}: invalid field ${param}', + ) + assert.throws( + () => validate(tx), + ValidationError, + '${txName}: invalid field ${param}', + ) + }) + +`; + } + + output = output.substring(0, output.length - 2); + output += "\n})\n"; + output = + `import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validate${txName} } from '../../src/models/transactions/${txName}' + +/** + * ${txName} Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('${txName}', function () { + let tx + + beforeEach(function () { + tx = ${getTx(txName)} as any + }) + + it('verifies valid ${txName}', function () { + assert.doesNotThrow(() => validate${txName}(tx)) + assert.doesNotThrow(() => validate(tx)) + }) + +` + output; + + return output; +} + +console.log(main());