diff --git a/packages/xahau/HISTORY.md b/packages/xahau/HISTORY.md index 40eef2b2..e1cdda3e 100644 --- a/packages/xahau/HISTORY.md +++ b/packages/xahau/HISTORY.md @@ -5,6 +5,7 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr ## Unreleased Changes ### Added +* Support for HookOnV2 Amendment * Support for IOUClaimReward Amendment * Support for NamedHooks * Support for the `simulate` RPC ([XLS-69](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069-simulate)) diff --git a/packages/xahau/src/models/common/xahau.ts b/packages/xahau/src/models/common/xahau.ts index d1bc82d4..b60bc7a8 100644 --- a/packages/xahau/src/models/common/xahau.ts +++ b/packages/xahau/src/models/common/xahau.ts @@ -86,6 +86,14 @@ export interface Hook { * The transactions that triggers the hook. Represented as a 256Hash */ HookOn?: string + /** + * The incoming transactions that triggers to the hook. Represented as a 256Hash + */ + HookOnIncoming?: string + /** + * The outgoing transactions that triggers from the hook. Represented as a 256Hash + */ + HookOnOutgoing?: string /** * The transactions that can emit from the hook. Represented as a 256Hash */ diff --git a/packages/xahau/src/models/ledger/HookDefinition.ts b/packages/xahau/src/models/ledger/HookDefinition.ts index ced34343..75c21ba9 100644 --- a/packages/xahau/src/models/ledger/HookDefinition.ts +++ b/packages/xahau/src/models/ledger/HookDefinition.ts @@ -27,6 +27,16 @@ export default interface HookDefintion */ HookOn?: string + /** + * The incoming transactions that triggers to the hook. Represented as a 256Hash + */ + HookOnIncoming?: string + + /** + * The outgoing transactions that triggers from the hook. Represented as a 256Hash + */ + HookOnOutgoing?: string + /** * The namespace of the hook. */ diff --git a/packages/xahau/src/models/transactions/setHook.ts b/packages/xahau/src/models/transactions/setHook.ts index 5cda97a0..b82078de 100644 --- a/packages/xahau/src/models/transactions/setHook.ts +++ b/packages/xahau/src/models/transactions/setHook.ts @@ -29,6 +29,7 @@ const HOOKNAME_REGEX = /^[0-9A-Fa-f]{8,32}$/u * @param tx - An SetHook Transaction. * @throws When the SetHook is Malformed. */ +// eslint-disable-next-line max-lines-per-function -- okay for this method export function validateSetHook(tx: Record): void { validateBaseTransaction(tx) @@ -45,12 +46,29 @@ export function validateSetHook(tx: Record): void { for (const hook of tx.Hooks) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be a Hook const hookObject = hook as Hook - const { HookOn, HookCanEmit, HookNamespace, HookName } = hookObject.Hook + const { + HookOn, + HookOnIncoming, + HookOnOutgoing, + HookCanEmit, + HookNamespace, + HookName, + } = hookObject.Hook if (HookOn !== undefined && !HEX_REGEX.test(HookOn)) { throw new ValidationError( `SetHook: HookOn in Hook must be a 256-bit (32-byte) hexadecimal value`, ) } + if (HookOnIncoming !== undefined && !HEX_REGEX.test(HookOnIncoming)) { + throw new ValidationError( + `SetHook: HookOnIncoming in Hook must be a 256-bit (32-byte) hexadecimal value`, + ) + } + if (HookOnOutgoing !== undefined && !HEX_REGEX.test(HookOnOutgoing)) { + throw new ValidationError( + `SetHook: HookOnOutgoing in Hook must be a 256-bit (32-byte) hexadecimal value`, + ) + } if (HookCanEmit !== undefined && !HEX_REGEX.test(HookCanEmit)) { throw new ValidationError( `SetHook: HookCanEmit in Hook must be a 256-bit (32-byte) hexadecimal value`, diff --git a/packages/xahau/test/integration/transactions/setHook.test.ts b/packages/xahau/test/integration/transactions/setHook.test.ts index 028fc751..08223037 100644 --- a/packages/xahau/test/integration/transactions/setHook.test.ts +++ b/packages/xahau/test/integration/transactions/setHook.test.ts @@ -93,4 +93,46 @@ describe('SetHook', function () { }, TIMEOUT, ) + + it( + 'hook on incoming/outgoing', + async () => { + const setHookTx: SetHook = { + TransactionType: 'SetHook', + Account: wallet.classicAddress, + Hooks: [ + { + Hook: { + CreateCode: acceptHook, + HookApiVersion: 0, + HookOnIncoming: '00'.repeat(32), + // eslint-disable-next-line no-inline-comments -- for readability + HookOnOutgoing: `01${'00'.repeat(31)}`, // should be different from HookOnIncoming + HookNamespace: '00'.repeat(32), + }, + }, + ], + } + await testTransaction(testContext.client, setHookTx, wallet) + + const ledgerEntryResponse = await testContext.client.request({ + command: 'ledger_entry', + hook: { account: wallet.classicAddress }, + }) + const node = ledgerEntryResponse.result.node as Hook + const hook = node.Hooks[0].Hook + const hookHash = hook.HookHash! + + const hookDefinitionResponse = await testContext.client.request({ + command: 'ledger_entry', + hook_definition: hookHash, + }) + const hookDefinitionNode = hookDefinitionResponse.result + .node as HookDefinition + expect(hookDefinitionNode.HookOn).toBeUndefined() + expect(hookDefinitionNode.HookOnIncoming).toBeDefined() + expect(hookDefinitionNode.HookOnOutgoing).toBeDefined() + }, + TIMEOUT, + ) }) diff --git a/packages/xahau/test/models/setHook.test.ts b/packages/xahau/test/models/setHook.test.ts index 6ce2617c..f40d7cd5 100644 --- a/packages/xahau/test/models/setHook.test.ts +++ b/packages/xahau/test/models/setHook.test.ts @@ -67,6 +67,10 @@ describe('SetHook', function () { '0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22', HookOn: 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7', + HookOnIncoming: + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7', + HookOnOutgoing: + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7', Flags: 1, HookApiVersion: 0, HookNamespace: @@ -94,30 +98,31 @@ describe('SetHook', function () { assert.throws(() => validate(setHookTx), ValidationError, errorMessage) }) - it(`throws w/ invalid HookOn in Hooks`, function () { - setHookTx.SignerQuorum = 2 - setHookTx.Hooks = [ - { - Hook: { - CreateCode: - '0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22', - HookOn: '', - Flags: 1, - HookApiVersion: 0, - HookNamespace: - '4FF9961269BF7630D32E15276569C94470174A5DA79FA567C0F62251AA9A36B9', + it.each(['HookOn', 'HookOnIncoming', 'HookOnOutgoing'])( + `throws w/ invalid %s in Hooks`, + function (field: string) { + setHookTx.Hooks = [ + { + Hook: { + CreateCode: + '0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22', + [field]: '', + Flags: 1, + HookApiVersion: 0, + HookNamespace: + '4FF9961269BF7630D32E15276569C94470174A5DA79FA567C0F62251AA9A36B9', + }, }, - }, - ] - const errorMessage = - 'SetHook: HookOn in Hook must be a 256-bit (32-byte) hexadecimal value' - assert.throws( - () => validateSetHook(setHookTx), - ValidationError, - errorMessage, - ) - assert.throws(() => validate(setHookTx), ValidationError, errorMessage) - }) + ] + const errorMessage = `SetHook: ${field} in Hook must be a 256-bit (32-byte) hexadecimal value` + assert.throws( + () => validateSetHook(setHookTx), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(setHookTx), ValidationError, errorMessage) + }, + ) it(`throws w/ invalid HookCanEmit in Hooks`, function () { setHookTx.Hooks = [