Skip to content

Instantly share code, notes, and snippets.

@KOSASIH
Last active May 29, 2023 04:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KOSASIH/77d504e982b74b65acee6f1af86145d6 to your computer and use it in GitHub Desktop.
Save KOSASIH/77d504e982b74b65acee6f1af86145d6 to your computer and use it in GitHub Desktop.

Notary

Notary hook collects signatures for multi-sign transactions. It has two modes of operation:

  1. Attach a proposed transaction to a memo and send it to the hook account
  2. Endorse an already proposed transaction by using its unique ID as invoice ID and sending a 1 drop payment to the hook.

It relies on the signer list on the account the hook is running on. Only accounts on this list can propose and endorse multisign transactions through this hook.

to test:

  • make sure Hooks Builder has at least 3 accounts: Alice, Bob and Carol
  • set up SignerListSet on Alice's account w/ SignerQuorum 2 to Bob & Carol
  • compile notary.c and deploy it to Alice account
  • open debug stream filtered on Alice and run propose.js; it asks for 2 parameters:
    1. notary account is where the hook is running, i.e. Alice
    2. proposer is the account authorized to propose a new transaction, e.g. Bob; the account secret is used to sign the transaction
  • see the hook debug stream, especially that it succeeds and produces and invoice ID (hex-quoted 32 bytes) for the other signers
  • save the invoice ID somewhere
  • run sign.js, as the other authorized user; it asks for 3 parameters:
    1. notary account is Alice, as above
    2. approver is the other authorized account, i.e. Carol
    3. invoice ID is the value saved above
  • see in the hook debug stream (and account balances) that the hook emited the multi-signed transaction

Modifying the example for more signers is left as an exercise for the reader.

// For documentation please see: https://xrpl-hooks.readme.io/reference/
// Generated using generate_error.sh
#ifndef HOOK_ERROR_CODES
#define SUCCESS 0
#define OUT_OF_BOUNDS -1
#define INTERNAL_ERROR -2
#define TOO_BIG -3
#define TOO_SMALL -4
#define DOESNT_EXIST -5
#define NO_FREE_SLOTS -6
#define INVALID_ARGUMENT -7
#define ALREADY_SET -8
#define PREREQUISITE_NOT_MET -9
#define FEE_TOO_LARGE -10
#define EMISSION_FAILURE -11
#define TOO_MANY_NONCES -12
#define TOO_MANY_EMITTED_TXN -13
#define NOT_IMPLEMENTED -14
#define INVALID_ACCOUNT -15
#define GUARD_VIOLATION -16
#define INVALID_FIELD -17
#define PARSE_ERROR -18
#define RC_ROLLBACK -19
#define RC_ACCEPT -20
#define NO_SUCH_KEYLET -21
#define NOT_AN_ARRAY -22
#define NOT_AN_OBJECT -23
#define INVALID_FLOAT -10024
#define DIVISION_BY_ZERO -25
#define MANTISSA_OVERSIZED -26
#define MANTISSA_UNDERSIZED -27
#define EXPONENT_OVERSIZED -28
#define EXPONENT_UNDERSIZED -29
#define OVERFLOW -30
#define NOT_IOU_AMOUNT -31
#define NOT_AN_AMOUNT -32
#define CANT_RETURN_NEGATIVE -33
#define NOT_AUTHORIZED -34
#define PREVIOUS_FAILURE_PREVENTS_RETRY -35
#define TOO_MANY_PARAMS -36
#define INVALID_TXN -37
#define RESERVE_INSUFFICIENT -38
#define COMPLEX_NOT_SUPPORTED -39
#define DOES_NOT_MATCH -40
#define HOOK_ERROR_CODES
#endif //HOOK_ERROR_CODES
// For documentation please see: https://xrpl-hooks.readme.io/reference/
// Generated using generate_extern.sh
#include <stdint.h>
#ifndef HOOK_EXTERN
extern int32_t
__attribute__((noduplicate))
_g(
uint32_t guard_id,
uint32_t maxiter
);
extern int64_t
accept(
uint32_t read_ptr,
uint32_t read_len,
int64_t error_code
);
extern int64_t
emit(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len
);
extern int64_t
etxn_burden (
void
);
extern int64_t
etxn_details(
uint32_t write_ptr,
uint32_t write_len
);
extern int64_t
etxn_fee_base(
uint32_t read_ptr,
uint32_t read_len
);
extern int64_t
etxn_generation (
void
);
extern int64_t
etxn_nonce(
uint32_t write_ptr,
uint32_t write_len
);
extern int64_t
etxn_reserve(
uint32_t count
);
extern int64_t
fee_base (
void
);
extern int64_t
float_compare(
int64_t float1,
int64_t float2,
uint32_t mode
);
extern int64_t
float_divide(
int64_t float1,
int64_t float2
);
extern int64_t
float_exponent(
int64_t float1
);
extern int64_t
float_exponent_set(
int64_t float1,
int32_t exponent
);
extern int64_t
float_int(
int64_t float1,
uint32_t decimal_places,
uint32_t abs
);
extern int64_t
float_invert(
int64_t float1
);
extern int64_t
float_log(
int64_t float1
);
extern int64_t
float_mantissa(
int64_t float1
);
extern int64_t
float_mantissa_set(
int64_t float1,
int64_t mantissa
);
extern int64_t
float_mulratio(
int64_t float1,
uint32_t round_up,
uint32_t numerator,
uint32_t denominator
);
extern int64_t
float_multiply(
int64_t float1,
int64_t float2
);
extern int64_t
float_negate(
int64_t float1
);
extern int64_t
float_one (
void
);
extern int64_t
float_root(
int64_t float1,
uint32_t n
);
extern int64_t
float_set(
int32_t exponent,
int64_t mantissa
);
extern int64_t
float_sign(
int64_t float1
);
extern int64_t
float_sign_set(
int64_t float1,
uint32_t negative
);
extern int64_t
float_sto(
uint32_t write_ptr,
uint32_t write_len,
uint32_t cread_ptr,
uint32_t cread_len,
uint32_t iread_ptr,
uint32_t iread_len,
int64_t float1,
uint32_t field_code
);
extern int64_t
float_sto_set(
uint32_t read_ptr,
uint32_t read_len
);
extern int64_t
float_sum(
int64_t float1,
int64_t float2
);
extern int64_t
hook_account(
uint32_t write_ptr,
uint32_t write_len
);
extern int64_t
hook_again (
void
);
extern int64_t
hook_hash(
uint32_t write_ptr,
uint32_t write_len,
int32_t hook_no
);
extern int64_t
hook_param(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len
);
extern int64_t
hook_param_set(
uint32_t read_ptr,
uint32_t read_len,
uint32_t kread_ptr,
uint32_t kread_len,
uint32_t hread_ptr,
uint32_t hread_len
);
extern int64_t
hook_pos (
void
);
extern int64_t
hook_skip(
uint32_t read_ptr,
uint32_t read_len,
uint32_t flags
);
extern int64_t
ledger_keylet(
uint32_t write_ptr,
uint32_t write_len,
uint32_t lread_ptr,
uint32_t lread_len,
uint32_t hread_ptr,
uint32_t hread_len
);
extern int64_t
ledger_last_hash(
uint32_t write_ptr,
uint32_t write_len
);
extern int64_t
ledger_last_time (
void
);
extern int64_t
ledger_nonce(
uint32_t write_ptr,
uint32_t write_len
);
extern int64_t
ledger_seq (
void
);
extern int64_t
meta_slot(
uint32_t slot_no
);
extern int64_t
otxn_burden (
void
);
extern int64_t
otxn_field(
uint32_t write_ptr,
uint32_t write_len,
uint32_t field_id
);
extern int64_t
otxn_field_txt(
uint32_t write_ptr,
uint32_t write_len,
uint32_t field_id
);
extern int64_t
otxn_generation (
void
);
extern int64_t
otxn_id(
uint32_t write_ptr,
uint32_t write_len,
uint32_t flags
);
extern int64_t
otxn_slot(
uint32_t slot_no
);
extern int64_t
otxn_type (
void
);
extern int64_t
rollback(
uint32_t read_ptr,
uint32_t read_len,
int64_t error_code
);
extern int64_t
slot(
uint32_t write_ptr,
uint32_t write_len,
uint32_t slot
);
extern int64_t
slot_clear(
uint32_t slot
);
extern int64_t
slot_count(
uint32_t slot
);
extern int64_t
slot_float(
uint32_t slot_no
);
extern int64_t
slot_id(
uint32_t write_ptr,
uint32_t write_len,
uint32_t slot
);
extern int64_t
slot_set(
uint32_t read_ptr,
uint32_t read_len,
int32_t slot
);
extern int64_t
slot_size(
uint32_t slot
);
extern int64_t
slot_subarray(
uint32_t parent_slot,
uint32_t array_id,
uint32_t new_slot
);
extern int64_t
slot_subfield(
uint32_t parent_slot,
uint32_t field_id,
uint32_t new_slot
);
extern int64_t
slot_type(
uint32_t slot_no,
uint32_t flags
);
extern int64_t
state(
uint32_t write_ptr,
uint32_t write_len,
uint32_t kread_ptr,
uint32_t kread_len
);
extern int64_t
state_foreign(
uint32_t write_ptr,
uint32_t write_len,
uint32_t kread_ptr,
uint32_t kread_len,
uint32_t nread_ptr,
uint32_t nread_len,
uint32_t aread_ptr,
uint32_t aread_len
);
extern int64_t
state_foreign_set(
uint32_t read_ptr,
uint32_t read_len,
uint32_t kread_ptr,
uint32_t kread_len,
uint32_t nread_ptr,
uint32_t nread_len,
uint32_t aread_ptr,
uint32_t aread_len
);
extern int64_t
state_set(
uint32_t read_ptr,
uint32_t read_len,
uint32_t kread_ptr,
uint32_t kread_len
);
extern int64_t
sto_emplace(
uint32_t write_ptr,
uint32_t write_len,
uint32_t sread_ptr,
uint32_t sread_len,
uint32_t fread_ptr,
uint32_t fread_len,
uint32_t field_id
);
extern int64_t
sto_erase(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len,
uint32_t field_id
);
extern int64_t
sto_subarray(
uint32_t read_ptr,
uint32_t read_len,
uint32_t array_id
);
extern int64_t
sto_subfield(
uint32_t read_ptr,
uint32_t read_len,
uint32_t field_id
);
extern int64_t
sto_validate(
uint32_t tread_ptr,
uint32_t tread_len
);
extern int64_t
trace(
uint32_t mread_ptr,
uint32_t mread_len,
uint32_t dread_ptr,
uint32_t dread_len,
uint32_t as_hex
);
extern int64_t
trace_float(
uint32_t read_ptr,
uint32_t read_len,
int64_t float1
);
extern int64_t
trace_num(
uint32_t read_ptr,
uint32_t read_len,
int64_t number
);
extern int64_t
trace_slot(
uint32_t read_ptr,
uint32_t read_len,
uint32_t slot
);
extern int64_t
util_accid(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len
);
extern int64_t
util_keylet(
uint32_t write_ptr,
uint32_t write_len,
uint32_t keylet_type,
uint32_t a,
uint32_t b,
uint32_t c,
uint32_t d,
uint32_t e,
uint32_t f
);
extern int64_t
util_raddr(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len
);
extern int64_t
util_sha512h(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len
);
extern int64_t
util_verify(
uint32_t dread_ptr,
uint32_t dread_len,
uint32_t sread_ptr,
uint32_t sread_len,
uint32_t kread_ptr,
uint32_t kread_len
);
#define HOOK_EXTERN
#endif //HOOK_EXTERN
/**
* Hook API include file
*
* Note to the reader:
* This include defines two types of things: external functions and macros
* Functions are used sparingly because a non-inlining compiler may produce
* undesirable output.
*
* Find documentation here: https://xrpl-hooks.readme.io/reference/
*/
#ifndef HOOKAPI_INCLUDED
#define HOOKAPI_INCLUDED 1
#define KEYLET_HOOK 1
#define KEYLET_HOOK_STATE 2
#define KEYLET_ACCOUNT 3
#define KEYLET_AMENDMENTS 4
#define KEYLET_CHILD 5
#define KEYLET_SKIP 6
#define KEYLET_FEES 7
#define KEYLET_NEGATIVE_UNL 8
#define KEYLET_LINE 9
#define KEYLET_OFFER 10
#define KEYLET_QUALITY 11
#define KEYLET_EMITTED_DIR 12
#define KEYLET_TICKET 13
#define KEYLET_SIGNERS 14
#define KEYLET_CHECK 15
#define KEYLET_DEPOSIT_PREAUTH 16
#define KEYLET_UNCHECKED 17
#define KEYLET_OWNER_DIR 18
#define KEYLET_PAGE 19
#define KEYLET_ESCROW 20
#define KEYLET_PAYCHAN 21
#define KEYLET_EMITTED 22
#define COMPARE_EQUAL 1U
#define COMPARE_LESS 2U
#define COMPARE_GREATER 4U
#include "error.h"
#include "extern.h"
#include "sfcodes.h"
#include "macro.h"
#endif
/**
* These are helper macros for writing hooks, all of them are optional as is including hookmacro.h at all
*/
#include <stdint.h>
#include "hookapi.h"
#include "sfcodes.h"
#ifndef HOOKMACROS_INCLUDED
#define HOOKMACROS_INCLUDED 1
#ifdef NDEBUG
#define DEBUG 0
#else
#define DEBUG 1
#endif
#define TRACEVAR(v) if (DEBUG) trace_num((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (int64_t)v);
#define TRACEHEX(v) if (DEBUG) trace((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (uint32_t)(v), (uint32_t)(sizeof(v)), 1);
#define TRACEXFL(v) if (DEBUG) trace_float((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (int64_t)v);
#define TRACESTR(v) if (DEBUG) trace((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (uint32_t)(v), sizeof(v), 0);
// hook developers should use this guard macro, simply GUARD(<maximum iterations>)
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
#define GUARDM(maxiter, n) _g(( (1ULL << 31U) + (__LINE__ << 16) + n), (maxiter)+1)
#define SBUF(str) (uint32_t)(str), sizeof(str)
#define REQUIRE(cond, str)\
{\
if (!(cond))\
rollback(SBUF(str), __LINE__);\
}
// make a report buffer as a c-string
// provide a name for a buffer to declare (buf)
// provide a static string
// provide an integer to print after the string
#define RBUF(buf, out_len, str, num)\
unsigned char buf[sizeof(str) + 21];\
int out_len = 0;\
{\
int i = 0;\
for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\
(buf)[i] = str[i];\
if ((buf)[sizeof(str)-1] == 0) i--;\
if ((num) < 0) (buf)[i++] = '-';\
uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\
uint64_t j = 10000000000000000000ULL;\
int start = 1;\
for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\
{\
unsigned char digit = ( unsigned_num / j ) % 10;\
if (digit == 0 && start)\
continue;\
start = 0;\
(buf)[i++] = '0' + digit;\
}\
(buf)[i] = '\0';\
out_len = i;\
}
#define RBUF2(buff, out_len, str, num, str2, num2)\
unsigned char buff[sizeof(str) + sizeof(str2) + 42];\
int out_len = 0;\
{\
unsigned char* __buf__ = buff;\
int i = 0;\
for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\
(__buf__)[i] = str[i];\
if ((__buf__)[sizeof(str)-1] == 0) i--;\
if ((num) < 0) (__buf__)[i++] = '-';\
uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\
uint64_t j = 10000000000000000000ULL;\
int start = 1;\
for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\
{\
unsigned char digit = ( unsigned_num / j ) % 10;\
if (digit == 0 && start)\
continue;\
start = 0;\
(__buf__)[i++] = '0' + digit;\
}\
__buf__ += i;\
out_len += i;\
i = 0;\
for (; GUARDM(sizeof(str2),3),i < sizeof(str2); ++i)\
(__buf__)[i] = str2[i];\
if ((__buf__)[sizeof(str2)-1] == 0) i--;\
if ((num2) < 0) (__buf__)[i++] = '-';\
unsigned_num = (uint64_t)( (num2) < 0 ? (num2) * -1 : (num2) );\
j = 10000000000000000000ULL;\
start = 1;\
for (; GUARDM(20,4), unsigned_num > 0 && j > 0; j /= 10)\
{\
unsigned char digit = ( unsigned_num / j ) % 10;\
if (digit == 0 && start)\
continue;\
start = 0;\
(__buf__)[i++] = '0' + digit;\
}\
(__buf__)[i] = '\0';\
out_len += i;\
}
#define CLEARBUF(b)\
{\
for (int x = 0; GUARD(sizeof(b)), x < sizeof(b); ++x)\
b[x] = 0;\
}
// returns an in64_t, negative if error, non-negative if valid drops
#define AMOUNT_TO_DROPS(amount_buffer)\
(((amount_buffer)[0] >> 7) ? -2 : (\
((((uint64_t)((amount_buffer)[0])) & 0xb00111111) << 56) +\
(((uint64_t)((amount_buffer)[1])) << 48) +\
(((uint64_t)((amount_buffer)[2])) << 40) +\
(((uint64_t)((amount_buffer)[3])) << 32) +\
(((uint64_t)((amount_buffer)[4])) << 24) +\
(((uint64_t)((amount_buffer)[5])) << 16) +\
(((uint64_t)((amount_buffer)[6])) << 8) +\
(((uint64_t)((amount_buffer)[7])))))
#define SUB_OFFSET(x) ((int32_t)(x >> 32))
#define SUB_LENGTH(x) ((int32_t)(x & 0xFFFFFFFFULL))
// when using this macro buf1len may be dynamic but buf2len must be static
// provide n >= 1 to indicate how many times the macro will be hit on the line of code
// e.g. if it is in a loop that loops 10 times n = 10
#define BUFFER_EQUAL_GUARD(output, buf1, buf1len, buf2, buf2len, n)\
{\
output = ((buf1len) == (buf2len) ? 1 : 0);\
for (int x = 0; GUARDM( (buf2len) * (n), 1 ), output && x < (buf2len);\
++x)\
output = (buf1)[x] == (buf2)[x];\
}
#define BUFFER_SWAP(x,y)\
{\
uint8_t* __z__ = x;\
x = y;\
y = __z__;\
}
#define ACCOUNT_COMPARE(compare_result, buf1, buf2)\
{\
compare_result = 0;\
for (int i = 0; GUARD(20), i < 20; ++i)\
{\
if (buf1[i] > buf2[i])\
{\
compare_result = 1;\
break;\
}\
else if (buf1[i] < buf2[i])\
{\
compare_result = -1;\
break;\
}\
}\
}
#define BUFFER_EQUAL_STR_GUARD(output, buf1, buf1len, str, n)\
BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), n)
#define BUFFER_EQUAL_STR(output, buf1, buf1len, str)\
BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), 1)
#define BUFFER_EQUAL(output, buf1, buf2, compare_len)\
BUFFER_EQUAL_GUARD(output, buf1, compare_len, buf2, compare_len, 1)
#define UINT16_TO_BUF(buf_raw, i)\
{\
unsigned char* __buf__ = (unsigned char*)buf_raw;\
__buf__[0] = (((uint64_t)i) >> 8) & 0xFFUL;\
__buf__[1] = (((uint64_t)i) >> 0) & 0xFFUL;\
}
#define UINT16_FROM_BUF(buf)\
(((uint64_t)((buf)[0]) << 8) +\
((uint64_t)((buf)[1]) << 0))
#define UINT32_TO_BUF(buf_raw, i)\
{\
unsigned char* __buf__ = (unsigned char*)buf_raw;\
__buf__[0] = (((uint64_t)i) >> 24) & 0xFFUL;\
__buf__[1] = (((uint64_t)i) >> 16) & 0xFFUL;\
__buf__[2] = (((uint64_t)i) >> 8) & 0xFFUL;\
__buf__[3] = (((uint64_t)i) >> 0) & 0xFFUL;\
}
#define UINT32_FROM_BUF(buf)\
(((uint64_t)((buf)[0]) << 24) +\
((uint64_t)((buf)[1]) << 16) +\
((uint64_t)((buf)[2]) << 8) +\
((uint64_t)((buf)[3]) << 0))
#define UINT64_TO_BUF(buf_raw, i)\
{\
unsigned char* __buf__ = (unsigned char*)buf_raw;\
__buf__[0] = (((uint64_t)i) >> 56) & 0xFFUL;\
__buf__[1] = (((uint64_t)i) >> 48) & 0xFFUL;\
__buf__[2] = (((uint64_t)i) >> 40) & 0xFFUL;\
__buf__[3] = (((uint64_t)i) >> 32) & 0xFFUL;\
__buf__[4] = (((uint64_t)i) >> 24) & 0xFFUL;\
__buf__[5] = (((uint64_t)i) >> 16) & 0xFFUL;\
__buf__[6] = (((uint64_t)i) >> 8) & 0xFFUL;\
__buf__[7] = (((uint64_t)i) >> 0) & 0xFFUL;\
}
#define UINT64_FROM_BUF(buf)\
(((uint64_t)((buf)[0]) << 56) +\
((uint64_t)((buf)[1]) << 48) +\
((uint64_t)((buf)[2]) << 40) +\
((uint64_t)((buf)[3]) << 32) +\
((uint64_t)((buf)[4]) << 24) +\
((uint64_t)((buf)[5]) << 16) +\
((uint64_t)((buf)[6]) << 8) +\
((uint64_t)((buf)[7]) << 0))
#define INT64_FROM_BUF(buf)\
((((uint64_t)((buf)[0] & 0x7FU) << 56) +\
((uint64_t)((buf)[1]) << 48) +\
((uint64_t)((buf)[2]) << 40) +\
((uint64_t)((buf)[3]) << 32) +\
((uint64_t)((buf)[4]) << 24) +\
((uint64_t)((buf)[5]) << 16) +\
((uint64_t)((buf)[6]) << 8) +\
((uint64_t)((buf)[7]) << 0)) * (buf[0] & 0x80U ? -1 : 1))
#define INT64_TO_BUF(buf_raw, i)\
{\
unsigned char* __buf__ = (unsigned char*)buf_raw;\
__buf__[0] = (((uint64_t)i) >> 56) & 0x7FUL;\
__buf__[1] = (((uint64_t)i) >> 48) & 0xFFUL;\
__buf__[2] = (((uint64_t)i) >> 40) & 0xFFUL;\
__buf__[3] = (((uint64_t)i) >> 32) & 0xFFUL;\
__buf__[4] = (((uint64_t)i) >> 24) & 0xFFUL;\
__buf__[5] = (((uint64_t)i) >> 16) & 0xFFUL;\
__buf__[6] = (((uint64_t)i) >> 8) & 0xFFUL;\
__buf__[7] = (((uint64_t)i) >> 0) & 0xFFUL;\
if (i < 0) __buf__[0] |= 0x80U;\
}
#define ttPAYMENT 0
#define ttESCROW_CREATE 1
#define ttESCROW_FINISH 2
#define ttACCOUNT_SET 3
#define ttESCROW_CANCEL 4
#define ttREGULAR_KEY_SET 5
#define ttOFFER_CREATE 7
#define ttOFFER_CANCEL 8
#define ttTICKET_CREATE 10
#define ttSIGNER_LIST_SET 12
#define ttPAYCHAN_CREATE 13
#define ttPAYCHAN_FUND 14
#define ttPAYCHAN_CLAIM 15
#define ttCHECK_CREATE 16
#define ttCHECK_CASH 17
#define ttCHECK_CANCEL 18
#define ttDEPOSIT_PREAUTH 19
#define ttTRUST_SET 20
#define ttACCOUNT_DELETE 21
#define ttHOOK_SET 22
#define ttNFTOKEN_MINT 25
#define ttNFTOKEN_BURN 26
#define ttNFTOKEN_CREATE_OFFER 27
#define ttNFTOKEN_CANCEL_OFFER 28
#define ttNFTOKEN_ACCEPT_OFFER 29
#define ttURITOKEN_MINT 45
#define ttURITOKEN_BURN 46
#define ttURITOKEN_BUY 47
#define ttURITOKEN_CREATE_SELL_OFFER 48
#define ttURITOKEN_CANCEL_SELL_OFFER 49
#define ttCLAIM_REWARD 98
#define ttINVOKE 99
#define ttAMENDMENT 100
#define ttFEE 101
#define ttUNL_MODIFY 102
#define ttEMIT_FAILURE 103
#define tfCANONICAL 0x80000000UL
#define atACCOUNT 1U
#define atOWNER 2U
#define atDESTINATION 3U
#define atISSUER 4U
#define atAUTHORIZE 5U
#define atUNAUTHORIZE 6U
#define atTARGET 7U
#define atREGULARKEY 8U
#define atPSEUDOCALLBACK 9U
#define amAMOUNT 1U
#define amBALANCE 2U
#define amLIMITAMOUNT 3U
#define amTAKERPAYS 4U
#define amTAKERGETS 5U
#define amLOWLIMIT 6U
#define amHIGHLIMIT 7U
#define amFEE 8U
#define amSENDMAX 9U
#define amDELIVERMIN 10U
#define amMINIMUMOFFER 16U
#define amRIPPLEESCROW 17U
#define amDELIVEREDAMOUNT 18U
/**
* RH NOTE -- PAY ATTENTION
*
* ALL 'ENCODE' MACROS INCREMENT BUF_OUT
* THIS IS TO MAKE CHAINING EASY
* BUF_OUT IS A SACRIFICIAL POINTER
*
* 'ENCODE' MACROS WITH CONSTANTS HAVE
* ALIASING TO ASSIST YOU WITH ORDER
* _TYPECODE_FIELDCODE_ENCODE_MACRO
* TO PRODUCE A SERIALIZED OBJECT
* IN CANONICAL FORMAT YOU MUST ORDER
* FIRST BY TYPE CODE THEN BY FIELD CODE
*
* ALL 'PREPARE' MACROS PRESERVE POINTERS
*
**/
#define ENCODE_TL_SIZE 49
#define ENCODE_TL(buf_out, tlamt, amount_type)\
{\
uint8_t __uat__ = amount_type; \
buf_out[0] = 0x60U +(__uat__ & 0x0FU ); \
for (int i = 1; GUARDM(48, 1), i < 49; ++i)\
buf_out[i] = tlamt[i-1];\
buf_out += ENCODE_TL_SIZE;\
}
#define _06_XX_ENCODE_TL(buf_out, drops, amount_type )\
ENCODE_TL(buf_out, drops, amount_type );
#define ENCODE_TL_AMOUNT(buf_out, drops )\
ENCODE_TL(buf_out, drops, amAMOUNT );
#define _06_01_ENCODE_TL_AMOUNT(buf_out, drops )\
ENCODE_TL_AMOUNT(buf_out, drops );
// Encode drops to serialization format
// consumes 9 bytes
#define ENCODE_DROPS_SIZE 9
#define ENCODE_DROPS(buf_out, drops, amount_type ) \
{\
uint8_t __uat__ = amount_type; \
uint64_t __udrops__ = drops; \
buf_out[0] = 0x60U +(__uat__ & 0x0FU ); \
buf_out[1] = 0b01000000 + (( __udrops__ >> 56 ) & 0b00111111 ); \
buf_out[2] = (__udrops__ >> 48) & 0xFFU; \
buf_out[3] = (__udrops__ >> 40) & 0xFFU; \
buf_out[4] = (__udrops__ >> 32) & 0xFFU; \
buf_out[5] = (__udrops__ >> 24) & 0xFFU; \
buf_out[6] = (__udrops__ >> 16) & 0xFFU; \
buf_out[7] = (__udrops__ >> 8) & 0xFFU; \
buf_out[8] = (__udrops__ >> 0) & 0xFFU; \
buf_out += ENCODE_DROPS_SIZE; \
}
#define _06_XX_ENCODE_DROPS(buf_out, drops, amount_type )\
ENCODE_DROPS(buf_out, drops, amount_type );
#define ENCODE_DROPS_AMOUNT(buf_out, drops )\
ENCODE_DROPS(buf_out, drops, amAMOUNT );
#define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\
ENCODE_DROPS_AMOUNT(buf_out, drops );
#define ENCODE_DROPS_FEE(buf_out, drops )\
ENCODE_DROPS(buf_out, drops, amFEE );
#define _06_08_ENCODE_DROPS_FEE(buf_out, drops )\
ENCODE_DROPS_FEE(buf_out, drops );
#define ENCODE_TT_SIZE 3
#define ENCODE_TT(buf_out, tt )\
{\
uint8_t __utt__ = tt;\
buf_out[0] = 0x12U;\
buf_out[1] =(__utt__ >> 8 ) & 0xFFU;\
buf_out[2] =(__utt__ >> 0 ) & 0xFFU;\
buf_out += ENCODE_TT_SIZE; \
}
#define _01_02_ENCODE_TT(buf_out, tt)\
ENCODE_TT(buf_out, tt);
#define ENCODE_ACCOUNT_SIZE 22
#define ENCODE_ACCOUNT(buf_out, account_id, account_type)\
{\
uint8_t __uat__ = account_type;\
buf_out[0] = 0x80U + __uat__;\
buf_out[1] = 0x14U;\
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\
*(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\
buf_out += ENCODE_ACCOUNT_SIZE;\
}
#define _08_XX_ENCODE_ACCOUNT(buf_out, account_id, account_type)\
ENCODE_ACCOUNT(buf_out, account_id, account_type);
#define ENCODE_ACCOUNT_SRC_SIZE 22
#define ENCODE_ACCOUNT_SRC(buf_out, account_id)\
ENCODE_ACCOUNT(buf_out, account_id, atACCOUNT);
#define _08_01_ENCODE_ACCOUNT_SRC(buf_out, account_id)\
ENCODE_ACCOUNT_SRC(buf_out, account_id);
#define ENCODE_ACCOUNT_DST_SIZE 22
#define ENCODE_ACCOUNT_DST(buf_out, account_id)\
ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION);
#define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\
ENCODE_ACCOUNT_DST(buf_out, account_id);
#define ENCODE_ACCOUNT_OWNER_SIZE 22
#define ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
ENCODE_ACCOUNT(buf_out, account_id, atOWNER);
#define _08_02_ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
ENCODE_ACCOUNT_OWNER(buf_out, account_id);
#define ENCODE_UINT32_COMMON_SIZE 5U
#define ENCODE_UINT32_COMMON(buf_out, i, field)\
{\
uint32_t __ui__ = i; \
uint8_t __uf__ = field; \
buf_out[0] = 0x20U +(__uf__ & 0x0FU); \
buf_out[1] =(__ui__ >> 24 ) & 0xFFU; \
buf_out[2] =(__ui__ >> 16 ) & 0xFFU; \
buf_out[3] =(__ui__ >> 8 ) & 0xFFU; \
buf_out[4] =(__ui__ >> 0 ) & 0xFFU; \
buf_out += ENCODE_UINT32_COMMON_SIZE; \
}
#define _02_XX_ENCODE_UINT32_COMMON(buf_out, i, field)\
ENCODE_UINT32_COMMON(buf_out, i, field)\
#define ENCODE_UINT32_UNCOMMON_SIZE 6U
#define ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
{\
uint32_t __ui__ = i; \
uint8_t __uf__ = field; \
buf_out[0] = 0x20U; \
buf_out[1] = __uf__; \
buf_out[2] =(__ui__ >> 24 ) & 0xFFU; \
buf_out[3] =(__ui__ >> 16 ) & 0xFFU; \
buf_out[4] =(__ui__ >> 8 ) & 0xFFU; \
buf_out[5] =(__ui__ >> 0 ) & 0xFFU; \
buf_out += ENCODE_UINT32_UNCOMMON_SIZE; \
}
#define _02_XX_ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
#define ENCODE_LLS_SIZE 6U
#define ENCODE_LLS(buf_out, lls )\
ENCODE_UINT32_UNCOMMON(buf_out, lls, 0x1B );
#define _02_27_ENCODE_LLS(buf_out, lls )\
ENCODE_LLS(buf_out, lls );
#define ENCODE_FLS_SIZE 6U
#define ENCODE_FLS(buf_out, fls )\
ENCODE_UINT32_UNCOMMON(buf_out, fls, 0x1A );
#define _02_26_ENCODE_FLS(buf_out, fls )\
ENCODE_FLS(buf_out, fls );
#define ENCODE_TAG_SRC_SIZE 5
#define ENCODE_TAG_SRC(buf_out, tag )\
ENCODE_UINT32_COMMON(buf_out, tag, 0x3U );
#define _02_03_ENCODE_TAG_SRC(buf_out, tag )\
ENCODE_TAG_SRC(buf_out, tag );
#define ENCODE_TAG_DST_SIZE 5
#define ENCODE_TAG_DST(buf_out, tag )\
ENCODE_UINT32_COMMON(buf_out, tag, 0xEU );
#define _02_14_ENCODE_TAG_DST(buf_out, tag )\
ENCODE_TAG_DST(buf_out, tag );
#define ENCODE_SEQUENCE_SIZE 5
#define ENCODE_SEQUENCE(buf_out, sequence )\
ENCODE_UINT32_COMMON(buf_out, sequence, 0x4U );
#define _02_04_ENCODE_SEQUENCE(buf_out, sequence )\
ENCODE_SEQUENCE(buf_out, sequence );
#define ENCODE_FLAGS_SIZE 5
#define ENCODE_FLAGS(buf_out, tag )\
ENCODE_UINT32_COMMON(buf_out, tag, 0x2U );
#define _02_02_ENCODE_FLAGS(buf_out, tag )\
ENCODE_FLAGS(buf_out, tag );
#define ENCODE_SIGNING_PUBKEY_SIZE 35
#define ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
{\
buf_out[0] = 0x73U;\
buf_out[1] = 0x21U;\
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(pkey + 0);\
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(pkey + 8);\
*(uint64_t*)(buf_out + 18) = *(uint64_t*)(pkey + 16);\
*(uint64_t*)(buf_out + 26) = *(uint64_t*)(pkey + 24);\
buf[34] = pkey[32];\
buf_out += ENCODE_SIGNING_PUBKEY_SIZE;\
}
#define _07_03_ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
ENCODE_SIGNING_PUBKEY(buf_out, pkey );
#define ENCODE_SIGNING_PUBKEY_NULL_SIZE 35
#define ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
{\
buf_out[0] = 0x73U;\
buf_out[1] = 0x21U;\
*(uint64_t*)(buf_out+2) = 0;\
*(uint64_t*)(buf_out+10) = 0;\
*(uint64_t*)(buf_out+18) = 0;\
*(uint64_t*)(buf_out+25) = 0;\
buf_out += ENCODE_SIGNING_PUBKEY_NULL_SIZE;\
}
#define _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
ENCODE_SIGNING_PUBKEY_NULL(buf_out );
#ifdef HAS_CALLBACK
#define PREPARE_PAYMENT_SIMPLE_SIZE 270U
#else
#define PREPARE_PAYMENT_SIMPLE_SIZE 248U
#endif
#define PREPARE_PAYMENT_SIMPLE(buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\
{\
uint8_t* __buf_out__ = buf_out_master;\
uint8_t __acc__[20];\
uint64_t __drops_amount__ = (drops_amount_raw);\
uint32_t __dest_tag__ = (dest_tag_raw);\
uint32_t __src_tag__ = (src_tag_raw);\
uint32_t __cls__ = (uint32_t)ledger_seq();\
hook_account(SBUF(__acc__));\
_01_02_ENCODE_TT (__buf_out__, ttPAYMENT ); /* uint16 | size 3 */ \
_02_02_ENCODE_FLAGS (__buf_out__, tfCANONICAL ); /* uint32 | size 5 */ \
_02_03_ENCODE_TAG_SRC (__buf_out__, __src_tag__ ); /* uint32 | size 5 */ \
_02_04_ENCODE_SEQUENCE (__buf_out__, 0 ); /* uint32 | size 5 */ \
_02_14_ENCODE_TAG_DST (__buf_out__, __dest_tag__ ); /* uint32 | size 5 */ \
_02_26_ENCODE_FLS (__buf_out__, __cls__ + 1 ); /* uint32 | size 6 */ \
_02_27_ENCODE_LLS (__buf_out__, __cls__ + 5 ); /* uint32 | size 6 */ \
_06_01_ENCODE_DROPS_AMOUNT (__buf_out__, __drops_amount__ ); /* amount | size 9 */ \
uint8_t* __fee_ptr__ = __buf_out__;\
_06_08_ENCODE_DROPS_FEE (__buf_out__, 0 ); /* amount | size 9 */ \
_07_03_ENCODE_SIGNING_PUBKEY_NULL (__buf_out__ ); /* pk | size 35 */ \
_08_01_ENCODE_ACCOUNT_SRC (__buf_out__, __acc__ ); /* account | size 22 */ \
_08_03_ENCODE_ACCOUNT_DST (__buf_out__, to_address ); /* account | size 22 */ \
int64_t __buf_size__ = PREPARE_PAYMENT_SIMPLE_SIZE - (__buf_out__ - buf_out_master); \
int64_t __edlen__ = etxn_details((uint32_t)__buf_out__, __buf_size__); /* emitdet | size 1?? */ \
int64_t __fee__ = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \
_06_08_ENCODE_DROPS_FEE (__fee_ptr__, __fee__ ); \
}
#ifdef HAS_CALLBACK
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 309
#else
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 288
#endif
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE(buf_out_master, tlamt, to_address, dest_tag_raw, src_tag_raw)\
{\
uint8_t* __buf_out__ = buf_out_master;\
uint8_t __acc__[20];\
uint32_t __dest_tag__ = (dest_tag_raw);\
uint32_t __src_tag__ = (src_tag_raw);\
uint32_t __cls__ = (uint32_t)ledger_seq();\
hook_account(SBUF(__acc__));\
_01_02_ENCODE_TT (__buf_out__, ttPAYMENT ); /* uint16 | size 3 */ \
_02_02_ENCODE_FLAGS (__buf_out__, tfCANONICAL ); /* uint32 | size 5 */ \
_02_03_ENCODE_TAG_SRC (__buf_out__, __src_tag__ ); /* uint32 | size 5 */ \
_02_04_ENCODE_SEQUENCE (__buf_out__, 0 ); /* uint32 | size 5 */ \
_02_14_ENCODE_TAG_DST (__buf_out__, __dest_tag__ ); /* uint32 | size 5 */ \
_02_26_ENCODE_FLS (__buf_out__, __cls__ + 1 ); /* uint32 | size 6 */ \
_02_27_ENCODE_LLS (__buf_out__, __cls__ + 5 ); /* uint32 | size 6 */ \
_06_01_ENCODE_TL_AMOUNT (__buf_out__, tlamt ); /* amount | size 48 */ \
uint8_t* __fee_ptr__ = __buf_out__;\
_06_08_ENCODE_DROPS_FEE (__buf_out__, 0 ); /* amount | size 9 */ \
_07_03_ENCODE_SIGNING_PUBKEY_NULL (__buf_out__ ); /* pk | size 35 */ \
_08_01_ENCODE_ACCOUNT_SRC (__buf_out__, __acc__ ); /* account | size 22 */ \
_08_03_ENCODE_ACCOUNT_DST (__buf_out__, to_address ); /* account | size 22 */ \
int64_t __buf_size__ = PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE - (__buf_out__ - buf_out_master); \
etxn_details((uint32_t)__buf_out__, __buf_size__); /* emitdet | size 1?? */ \
int64_t __fee__ = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); \
_06_08_ENCODE_DROPS_FEE (__fee_ptr__, __fee__ ); \
}
#endif
/**
* Notary.c - An example hook for collecting signatures for multi-sign transactions without blocking sequence number
* on the account.
*
* Author: Richard Holland
* Date: 11 Feb 2021
*
**/
#include <stdint.h>
#include "hookapi.h"
// maximum tx blob
#define MAX_MEMO_SIZE 4096
// LastLedgerSeq must be this far ahead of current to submit a new txn blob
#define MINIMUM_FUTURE_LEDGER 60
/**
* Notary - easy multisign with Hooks
* Two modes of operation:
* 1. Attach a proposed transaction to a memo and send it to the hook account
* 2. Endorse an already proposed transaction by using its unique ID as invoice ID and sending a 1 drop payment
* to the hook.
*
* This hook relies on the signer list on the account the hook is running on.
* Only accounts on this list can propose and endorse multisign transactions through this Hook.
*/
int64_t hook(uint32_t reserved)
{
TRACESTR("enter hook");
// this api fetches the AccountID of the account the hook currently executing is installed on
// since hooks can be triggered by both incoming and outgoing transactions this is important to know
unsigned char hook_accid[20];
hook_account((uint32_t)hook_accid, 20);
etxn_reserve(1);
// next fetch the sfAccount field from the originating transaction
uint8_t account_field[20];
int32_t account_field_len = otxn_field(SBUF(account_field), sfAccount);
if (account_field_len < 20) // negative values indicate errors from every api
rollback(SBUF("Notary: sfAccount field missing!!!"), 10); // this code could never be hit in prod
// but it's here for completeness
// compare the "From Account" (sfAccount) on the transaction with the account the hook is running on
int equal = 0; BUFFER_EQUAL(equal, hook_accid, account_field, 20);
if (equal)
accept(SBUF("Notary: Outgoing transaction"), 20);
uint8_t tx_blob[MAX_MEMO_SIZE];
int64_t tx_len = 0;
uint8_t invoice_id[32];
int64_t invoice_id_len =
otxn_field(SBUF(invoice_id), sfInvoiceID);
// check if an invoice ID was provided... this would be mode 2 above
if (invoice_id_len == 32)
{
// it was, so this is an attempt at endorsing an existing proposed multisig transaction
// attempt to retrieve the proposed txn blob from the Hook State by setting the last nibble of the invoice ID
// to `F` and using it as state key
invoice_id[31] = ( invoice_id[31] & 0xF0U ) + 0x0FU;
tx_len = state(SBUF(tx_blob), SBUF(invoice_id));
if (tx_len < 0)
rollback(SBUF("Notary: Received invoice id that did not correspond to a submitted multisig txn."), 1);
// proposed txn exists... but it may have expired so we need to check that first
int64_t lls_lookup = sto_subfield(tx_blob, tx_len, sfLastLedgerSequence);
uint8_t* lls_ptr = SUB_OFFSET(lls_lookup) + tx_blob;
uint32_t lls_len = SUB_LENGTH(lls_lookup);
if (lls_len != 4 || UINT32_FROM_BUF(lls_ptr) < ledger_seq())
{
// expired or invalid tx, purging
if (state_set(0, 0, SBUF(invoice_id)) < 0)
rollback(SBUF("Notary: Error erasing old txn blob."), 40);
accept(SBUF("Notary: Multisig txn was too old (last ledger seq passed) and was erased."), 1);
}
// execution to here means the invoice ID corresponded to a currently valid proposed multisig transaction
// that exists in the Hook State for this account
// however we still need to check if this user is on the signer list before proceeding.
}
// check for the presence of a memo
uint8_t memos[MAX_MEMO_SIZE];
int64_t memos_len = otxn_field(SBUF(memos), sfMemos);
uint32_t payload_len = 0;
uint8_t* payload_ptr = 0;
// if there is a memo present then we are in mode 1 above, but we need to ensure the user isn't invoking
// undefined behaviour by making them pick either mode 1 or mode 2:
if (memos_len <= 0 && invoice_id_len <= 0)
accept(SBUF("Notary: Incoming txn with neither memo nor invoice ID, passing."), 0);
if (memos_len > 0 && invoice_id_len > 0)
rollback(SBUF("Notary: Incoming txn with both memo and invoice ID, abort."), 0);
// now check if the sender is on the signer list
// we can do this by first creating a keylet that describes the signer list on the hook account
uint8_t keylet[34];
CLEARBUF(keylet);
if (util_keylet(SBUF(keylet), KEYLET_SIGNERS, SBUF(hook_accid), 0, 0, 0, 0) != 34)
rollback(SBUF("Notary: Internal error, could not generate keylet"), 10);
// then requesting XRPLD slot that keylet into a new slot for us
int64_t slot_no = slot_set(SBUF(keylet), 0);
TRACEVAR(slot_no);
if (slot_no < 0)
rollback(SBUF("Notary: Could not set keylet in slot"), 10);
// once slotted we can examine the signer list object
// the first field we are interested in is the required quorum to actually pass a multisign transaction
int64_t result = slot_subfield(slot_no, sfSignerQuorum, 0);
if (result < 0)
rollback(SBUF("Notary: Could not find sfSignerQuorum on hook account"), 20);
// we will retrieve the 4 byte quorum into a buffer, in future the will be a shortcut for this
uint32_t signer_quorum = 0;
uint8_t buf[4];
result = slot(SBUF(buf), result);
if (result != 4)
rollback(SBUF("Notary: Could not fetch sfSignerQuorum from sfSignerEntries."), 80);
// then conver the four byte buffer to an unsigned 32 bit integer
signer_quorum = UINT32_FROM_BUF(buf);
TRACEVAR(signer_quorum); // print the integer for debugging purposes
// next we want to examine the signer entries, we can do this by loading the signer entries field into a new slot
// or in this case we'll just reuse the existing slot since we're done with the parent object.
result = slot_subfield(slot_no, sfSignerEntries, slot_no);
if (result < 0)
rollback(SBUF("Notary: Could not find sfSignerEntries on hook account"), 20);
// since sfSignerEntries is an array type we can request its length with slot_count
int64_t signer_count = slot_count(slot_no);
if (signer_count < 0)
rollback(SBUF("Notary: Could not fetch sfSignerEntries count"), 30);
// now we need to iterate through all the signers in the signer entries array
// if the account that created the originating transaction is in the list then we can pass here
// otherwise we must rollback because the account is unauthorized
int subslot = 0;
uint8_t found = 0;
uint16_t signer_weight = 0;
for (int i = 0; GUARD(8), i < signer_count + 1; ++i)
{
// load the next array entry into a slot
subslot = slot_subarray(slot_no, i, subslot);
if (subslot < 0)
rollback(SBUF("Notary: Could not fetch one of the sfSigner entries [subarray]."), 40);
// load the account field from that entry into a new slot
result = slot_subfield(subslot, sfAccount, 0);
if (result < 0)
rollback(SBUF("Notary: Could not fetch one of the account entires in sfSigner."), 50);
// dump the new slot into a buffer
uint8_t signer_account[20];
result = slot(SBUF(signer_account), result);
if (result != 20)
rollback(SBUF("Notary: Could not fetch one of the sfSigner entries [slot sfAccount]."), 60);
// load the weight field into a new slot
result = slot_subfield(subslot, sfSignerWeight, 0);
if (result < 0)
rollback(SBUF("Notary: Could not fetch sfSignerWeight from sfSignerEntry."), 70);
// dump the weight field into a buffer
result = slot(buf, 2, result);
if (result != 2)
rollback(SBUF("Notary: Could not fetch sfSignerWeight from sfSignerEntry."), 80);
// convert weight buffer to an integer
signer_weight = UINT16_FROM_BUF(buf);
// some debug output to see the progress
TRACEVAR(signer_weight);
TRACEHEX(account_field);
TRACEHEX(signer_account);
// compare the signer account for this signer entry against the originating transaction (sending) account
int equal = 0;
BUFFER_EQUAL_GUARD(equal, signer_account, 20, account_field, 20, 8);
if (equal)
{
// if the otxn account was in the signer list we can stop iterating
found = i + 1;
break;
}
}
// ensure the otxn account is authed
if (!found)
rollback(SBUF("Notary: Your account was not present in the signer list."), 70);
// execution to this point means the following:
// 1. the originating transaction (sending) account is authorized as one of the signers on the hook account
// 2. either an invoice ID or a memo was sent to the hook (but not both).
// if a memo was sent to the hook it must be mode 1 above (proposing a new multisign transaction)
if (memos_len > 0)
{
// this is a defensive check, it is actually never executed due to an identical condition above
if (invoice_id_len > 0)
rollback(SBUF("Notary: Incoming transaction with both invoice id and memo. Aborting."), 0);
// since our memos are in a buffer inside the hook (as opposed to being a slot) we use the sto api with it
// the sto apis probe into a serialized object returning offsets and lengths of subfields or array entries
int64_t memo_lookup = sto_subarray(memos, memos_len, 0);
uint8_t* memo_ptr = SUB_OFFSET(memo_lookup) + memos;
uint32_t memo_len = SUB_LENGTH(memo_lookup);
// memos are nested inside an actual memo object, so we need to subfield
// equivalently in JSON this would look like memo_array[i]["Memo"]
memo_lookup = sto_subfield(memo_ptr, memo_len, sfMemo);
memo_ptr = SUB_OFFSET(memo_lookup) + memo_ptr;
memo_len = SUB_LENGTH(memo_lookup);
if (memo_lookup < 0)
rollback(SBUF("Notary: Incoming txn had a blank sfMemos, abort."), 1);
int64_t format_lookup = sto_subfield(memo_ptr, memo_len, sfMemoFormat);
uint8_t* format_ptr = SUB_OFFSET(format_lookup) + memo_ptr;
uint32_t format_len = SUB_LENGTH(format_lookup);
int is_unsigned_payload = 0;
BUFFER_EQUAL_STR_GUARD(is_unsigned_payload, format_ptr, format_len, "unsigned/payload+1", 1);
if (!is_unsigned_payload)
accept(SBUF("Notary: Memo is an invalid format. Passing txn."), 50);
int64_t data_lookup = sto_subfield(memo_ptr, memo_len, sfMemoData);
uint8_t* data_ptr = SUB_OFFSET(data_lookup) + memo_ptr;
uint32_t data_len = SUB_LENGTH(data_lookup);
if (data_len > MAX_MEMO_SIZE)
rollback(SBUF("Notary: Memo too large (4kib max)."), 4);
// inspect unsigned payload
// first check that sfTransactionType appears in the memo... if it doesn't then it can't be a transaction
int64_t txtype_lookup = sto_subfield(data_ptr, data_len, sfTransactionType);
if (txtype_lookup < 0)
rollback(SBUF("Notary: Memo is invalid format. Should be an unsigned transaction."), 2);
// next check the lastLedgerSequence is sensibly set otherwise there will be no chance for the other signers
// to endorse the txn before it expires
int64_t lls_lookup = sto_subfield(data_ptr, data_len, sfLastLedgerSequence);
uint8_t* lls_ptr = SUB_OFFSET(lls_lookup) + data_ptr;
uint32_t lls_len = SUB_LENGTH(lls_lookup);
// check for expired txn
if (lls_len != 4 || UINT32_FROM_BUF(lls_ptr) < ledger_seq() + MINIMUM_FUTURE_LEDGER)
rollback(SBUF("Notary: Provided txn blob expires too soo (LastLedgerSeq)."), 3);
// compute txn hash, this becomes the ID passed as an invoice ID by the endorsers (other signers)
if (util_sha512h(SBUF(invoice_id), data_ptr, data_len) < 0)
rollback(SBUF("Notary: Could not compute sha512 over the submitted txn."), 5);
TRACEHEX(invoice_id);
invoice_id[31] = ( invoice_id[31] & 0xF0U ) + 0x0FU;
// write blob to state... the state key for the txn blob is the txn ID with `F` as the last nibble.
int64_t ssrv = state_set(data_ptr, data_len, SBUF(invoice_id));
if (ssrv < 0) {
TRACEVAR(ssrv);
rollback(SBUF("Notary: Could not write txn to hook state."), 6);
}
}
// execution to here means if we were in mode 1 we now drop into mode 2, because the proposed txn is now recorded
// so we simply treat this as an endorsement (mode 2) from here...
// record the signature... the state key for this is the txn ID with (1 + signer number) as the last nibble
invoice_id[31] = ( invoice_id[31] & 0xF0U ) + found;
// the value we record against the signer is his/her signer weight at the time the endorsement or proposal happened
UINT16_TO_BUF(buf, signer_weight);
if (state_set(buf, 2, SBUF(invoice_id)) != 2)
rollback(SBUF("Notary: Could not write signature to hook state."), 7);
// check if we have managed to achieve a quorum by loading all current signatures and adding together the signer
// weights (stored as the HookState values)
uint32_t total = 0;
for (uint8_t i = 1; GUARD(8), i < 9; ++i)
{
invoice_id[31] = ( invoice_id[31] & 0xF0U ) + i;
if (state(buf, 2, SBUF(invoice_id)) == 2)
total += UINT16_FROM_BUF(buf);
}
TRACEVAR(total);
TRACEVAR(signer_quorum);
// if we haven't achieved a quorum we will output the ID as the hook result string so it can be given to the
// other endorsers
if (total < signer_quorum)
{
uint8_t header[] = "Notary: Accepted waiting for other signers...: ";
uint8_t returnval[112];
uint8_t* ptr = returnval;
for (int i = 0; GUARD(47), i < 47; ++i)
*ptr++ = header[i];
for (int i = 0; GUARD(32),i < 32; ++i)
{
uint8_t hi = (invoice_id[i] >> 4U);
uint8_t lo = (invoice_id[i] & 0xFU);
hi += ( hi > 9 ? ('A'-10) : '0' );
lo += ( lo > 9 ? ('A'-10) : '0' );
*ptr++ = hi;
*ptr++ = lo;
}
accept(SBUF(returnval), 0);
}
// execution to here means we achieved a quorum on a proposed txn
// therefore we must now emit the txn then garbage collect the old state
int should_emit = 1;
invoice_id[31] = ( invoice_id[31] & 0xF0U ) + 0x0FU;
tx_len = state(SBUF(tx_blob), SBUF(invoice_id));
if (tx_len < 0)
should_emit = 0;
// delete everything from state before emitting
state_set(0, 0, SBUF(invoice_id));
for (uint8_t i = 1; GUARD(8), i < 9; ++i)
{
invoice_id[31] = ( invoice_id[31] & 0xF0U ) + i;
state_set(0, 0, SBUF(invoice_id));
}
if (!should_emit)
rollback(SBUF("Notary: Tried to emit multisig txn but it was missing"), 1);
// blob exists, check expiry
int64_t lls_lookup = sto_subfield(tx_blob, tx_len, sfLastLedgerSequence);
uint8_t* lls_ptr = SUB_OFFSET(lls_lookup) + tx_blob;
uint32_t lls_len = SUB_LENGTH(lls_lookup);
if (lls_len != 4)
rollback(SBUF("Notary: Was about to emit txn but it doesn't have LastLedgerSequence"), 1);
uint32_t lls_old = UINT32_FROM_BUF(lls_ptr);
if (lls_old < ledger_seq())
rollback(SBUF("Notary: Was about to emit txn but it's too old now"), 1);
// modify the txn for emission
// we need to remove sfSigners if it exists
// we need to zero sfSequence sfSigningPubKey and sfTxnSignature
// we need to correctly set sfFirstLedgerSequence
// first do the erasure, this can fail if there is no such sfSigner field, so swap buffers to immitate success
uint8_t buffer[MAX_MEMO_SIZE];
uint8_t* buffer2 = buffer;
uint8_t* buffer1 = tx_blob;
result = sto_erase(buffer2, MAX_MEMO_SIZE, buffer1, tx_len, sfSigners);
if (result > 0)
tx_len = result;
else
BUFFER_SWAP(buffer1, buffer2);
// next zero sfSequence
uint8_t zeroed[6];
CLEARBUF(zeroed);
zeroed[0] = 0x24U; // this is the lead byte for sfSequence
tx_len = sto_emplace(buffer1, MAX_MEMO_SIZE, buffer2, tx_len, zeroed, 5, sfSequence);
if (tx_len <= 0)
rollback(SBUF("Notary: Emplacing sfSequence failed."), 1);
// next set sfTxnSignature to 0
zeroed[0] = 0x74U; // lead byte for sfTxnSignature, next byte is length which is 0
tx_len = sto_emplace(buffer2, MAX_MEMO_SIZE, buffer1, tx_len, zeroed, 2, sfTxnSignature);
TRACEVAR(tx_len);
if (tx_len <= 0)
rollback(SBUF("Notary: Emplacing sfTxnSignature failed."), 1);
// next set sfSigningPubKey to 0
zeroed[0] = 0x73U; // this is the lead byte for sfSigningPubkey, note that the next byte is 0 which is the length
tx_len = sto_emplace(buffer1, MAX_MEMO_SIZE, buffer2, tx_len, zeroed, 2, sfSigningPubKey);
TRACEVAR(tx_len);
if (tx_len <= 0)
rollback(SBUF("Notary: Emplacing sfSigningPubKey failed."), 1);
// finally set FirstLedgerSeq appropriately
uint32_t fls = ledger_seq() + 1;
zeroed[0] = 0x20U;
zeroed[1] = 0x1AU;
UINT32_TO_BUF(zeroed + 2, fls);
tx_len = sto_emplace(buffer2, MAX_MEMO_SIZE, buffer1, tx_len, zeroed, 6, sfFirstLedgerSequence);
if (tx_len <= 0)
rollback(SBUF("Notary: Emplacing sfFirstLedgerSequence failed."), 1);
uint32_t lls_new = fls + 4;
if (lls_old > lls_new) {
trace("fixing", 6, buffer2, tx_len, 1);
tx_len = sto_erase(buffer1, MAX_MEMO_SIZE, buffer2, tx_len, sfLastLedgerSequence);
if (tx_len <= 0)
rollback(SBUF("Notary: Erasing sfLastLedgerSequence failed."), 1);
zeroed[1] = 0x1BU;
UINT32_TO_BUF(zeroed + 2, lls_new);
tx_len = sto_emplace(buffer2, MAX_MEMO_SIZE, buffer1, tx_len, zeroed, 6, sfLastLedgerSequence);
if (tx_len <= 0)
rollback(SBUF("Notary: Emplacing sfLastLedgerSequence failed."), 1);
}
// finally add emit details
uint8_t emitdet[138];
result = etxn_details(SBUF(emitdet));
if (result < 0)
rollback(SBUF("Notary: EmitDetails failed to generate."), 1);
tx_len = sto_emplace(buffer1, MAX_MEMO_SIZE, buffer2, tx_len, emitdet, result, sfEmitDetails);
if (tx_len < 0)
rollback(SBUF("Notary: Emplacing sfEmitDetails failed."), 1);
// replace fee with something currently appropriate
uint8_t fee[ENCODE_DROPS_SIZE];
uint8_t* fee_ptr = fee; // this ptr is incremented by the macro, so just throw it away
int64_t fee_to_pay = etxn_fee_base(buffer1, tx_len);
if (fee_to_pay < 0)
rollback(SBUF("Notary: Computing sfFee failed."), 1);
ENCODE_DROPS(fee_ptr, fee_to_pay, amFEE);
tx_len = sto_emplace(buffer2, MAX_MEMO_SIZE, buffer1, tx_len, SBUF(fee), sfFee);
if (tx_len <= 0)
rollback(SBUF("Notary: Emplacing sfFee failed."), 1);
uint8_t emithash[32];
int64_t erv = emit(SBUF(emithash), buffer2, tx_len);
if (erv < 0) {
TRACEVAR(erv);
accept(SBUF("Notary: All conditions met but emission failed: proposed txn was malformed."), 1);
}
accept(SBUF("Notary: Emitted multisigned txn"), 0);
return 0;
}
import { XrplClient } from "https://esm.sh/xrpl-client?bundle";
const lib = require("xrpl-accountlib");
const bin = require("ripple-binary-codec");
const keypairs = require("ripple-keypairs");
/**
* @input {Account} notary_account Notary Account
* @input {Account.secret} proposer_secret Proposer Account
*/
const { notary_account, proposer_secret } = process.env
const proposer_keypair = lib.derive.familySeed(proposer_secret);
const proposer_account = keypairs.deriveAddress(proposer_keypair.keypair.publicKey);
const client = new XrplClient('wss://hooks-testnet-v3.xrpl-labs.com');
const main = async () => {
console.log('notary_account', notary_account);
const proposed_tx = {
TransactionType: 'Payment',
Account: notary_account,
Amount: '100',
Destination: proposer_account,
DestinationTag: '42',
LastLedgerSequence: "4000000000",
Fee: '0',
Sequence: 0,
NetworkID: "21338"
};
const inner_tx = bin.encode(proposed_tx);
const { account_data } = await client.send({ command: 'account_info', 'account': proposer_account });
if (!account_data) {
console.log('Proposer account not found.');
client.close();
return;
}
const tx = {
TransactionType: 'Payment',
Account: proposer_account,
Amount: '1',
Destination: notary_account,
Fee: '12000000',
Memos: [
{
Memo: {
MemoData: inner_tx,
MemoFormat: "unsigned/payload+1",
MemoType: "notary/proposed"
}
}
],
Sequence: account_data.Sequence,
NetworkID: "21338"
};
hexlify_memos(tx);
const {signedTransaction} = lib.sign(tx, proposer_keypair);
const submit = await client.send({ command: 'submit', 'tx_blob': signedTransaction });
console.log(submit);
console.log('Shutting down...');
client.close();
};
function hexlify_memos(x)
{
if (!("Memos" in x))
return;
for (let y = 0; y < x["Memos"].length; ++y)
{
let Memo = x["Memos"][y]["Memo"];
let Fields = ["MemoFormat", "MemoType", "MemoData"];
for (let z = 0; z < Fields.length; ++z)
{
if (Fields[z] in Memo)
{
let u = Memo[Fields[z]].toUpperCase()
if (u.match(/^[0-9A-F]+$/))
{
Memo[Fields[z]] = u;
continue;
}
let v = Memo[Fields[z]], q = "";
for (let i = 0; i < v.length; ++i)
{
q += Number(v.charCodeAt(i)).toString(16).padStart(2, '0');
}
Memo[Fields[z]] = q.toUpperCase();
}
}
}
}
main()
// For documentation please see: https://xrpl-hooks.readme.io/reference/
// Generated using generate_sfcodes.sh
#define sfCloseResolution ((16U << 16U) + 1U)
#define sfMethod ((16U << 16U) + 2U)
#define sfTransactionResult ((16U << 16U) + 3U)
#define sfTickSize ((16U << 16U) + 16U)
#define sfUNLModifyDisabling ((16U << 16U) + 17U)
#define sfHookResult ((16U << 16U) + 18U)
#define sfLedgerEntryType ((1U << 16U) + 1U)
#define sfTransactionType ((1U << 16U) + 2U)
#define sfSignerWeight ((1U << 16U) + 3U)
#define sfTransferFee ((1U << 16U) + 4U)
#define sfVersion ((1U << 16U) + 16U)
#define sfHookStateChangeCount ((1U << 16U) + 17U)
#define sfHookEmitCount ((1U << 16U) + 18U)
#define sfHookExecutionIndex ((1U << 16U) + 19U)
#define sfHookApiVersion ((1U << 16U) + 20U)
#define sfNetworkID ((2U << 16U) + 1U)
#define sfFlags ((2U << 16U) + 2U)
#define sfSourceTag ((2U << 16U) + 3U)
#define sfSequence ((2U << 16U) + 4U)
#define sfPreviousTxnLgrSeq ((2U << 16U) + 5U)
#define sfLedgerSequence ((2U << 16U) + 6U)
#define sfCloseTime ((2U << 16U) + 7U)
#define sfParentCloseTime ((2U << 16U) + 8U)
#define sfSigningTime ((2U << 16U) + 9U)
#define sfExpiration ((2U << 16U) + 10U)
#define sfTransferRate ((2U << 16U) + 11U)
#define sfWalletSize ((2U << 16U) + 12U)
#define sfOwnerCount ((2U << 16U) + 13U)
#define sfDestinationTag ((2U << 16U) + 14U)
#define sfHighQualityIn ((2U << 16U) + 16U)
#define sfHighQualityOut ((2U << 16U) + 17U)
#define sfLowQualityIn ((2U << 16U) + 18U)
#define sfLowQualityOut ((2U << 16U) + 19U)
#define sfQualityIn ((2U << 16U) + 20U)
#define sfQualityOut ((2U << 16U) + 21U)
#define sfStampEscrow ((2U << 16U) + 22U)
#define sfBondAmount ((2U << 16U) + 23U)
#define sfLoadFee ((2U << 16U) + 24U)
#define sfOfferSequence ((2U << 16U) + 25U)
#define sfFirstLedgerSequence ((2U << 16U) + 26U)
#define sfLastLedgerSequence ((2U << 16U) + 27U)
#define sfTransactionIndex ((2U << 16U) + 28U)
#define sfOperationLimit ((2U << 16U) + 29U)
#define sfReferenceFeeUnits ((2U << 16U) + 30U)
#define sfReserveBase ((2U << 16U) + 31U)
#define sfReserveIncrement ((2U << 16U) + 32U)
#define sfSetFlag ((2U << 16U) + 33U)
#define sfClearFlag ((2U << 16U) + 34U)
#define sfSignerQuorum ((2U << 16U) + 35U)
#define sfCancelAfter ((2U << 16U) + 36U)
#define sfFinishAfter ((2U << 16U) + 37U)
#define sfSignerListID ((2U << 16U) + 38U)
#define sfSettleDelay ((2U << 16U) + 39U)
#define sfTicketCount ((2U << 16U) + 40U)
#define sfTicketSequence ((2U << 16U) + 41U)
#define sfNFTokenTaxon ((2U << 16U) + 42U)
#define sfMintedNFTokens ((2U << 16U) + 43U)
#define sfBurnedNFTokens ((2U << 16U) + 44U)
#define sfHookStateCount ((2U << 16U) + 45U)
#define sfEmitGeneration ((2U << 16U) + 46U)
#define sfLockCount ((2U << 16U) + 47U)
#define sfRewardTime ((2U << 16U) + 98U)
#define sfRewardLgrFirst ((2U << 16U) + 99U)
#define sfRewardLgrLast ((2U << 16U) + 100U)
#define sfIndexNext ((3U << 16U) + 1U)
#define sfIndexPrevious ((3U << 16U) + 2U)
#define sfBookNode ((3U << 16U) + 3U)
#define sfOwnerNode ((3U << 16U) + 4U)
#define sfBaseFee ((3U << 16U) + 5U)
#define sfExchangeRate ((3U << 16U) + 6U)
#define sfLowNode ((3U << 16U) + 7U)
#define sfHighNode ((3U << 16U) + 8U)
#define sfDestinationNode ((3U << 16U) + 9U)
#define sfCookie ((3U << 16U) + 10U)
#define sfServerVersion ((3U << 16U) + 11U)
#define sfNFTokenOfferNode ((3U << 16U) + 12U)
#define sfEmitBurden ((3U << 16U) + 13U)
#define sfHookInstructionCount ((3U << 16U) + 17U)
#define sfHookReturnCode ((3U << 16U) + 18U)
#define sfReferenceCount ((3U << 16U) + 19U)
#define sfRewardAccumulator ((3U << 16U) + 100U)
#define sfEmailHash ((4U << 16U) + 1U)
#define sfTakerPaysCurrency ((10U << 16U) + 1U)
#define sfTakerPaysIssuer ((10U << 16U) + 2U)
#define sfTakerGetsCurrency ((10U << 16U) + 3U)
#define sfTakerGetsIssuer ((10U << 16U) + 4U)
#define sfLedgerHash ((5U << 16U) + 1U)
#define sfParentHash ((5U << 16U) + 2U)
#define sfTransactionHash ((5U << 16U) + 3U)
#define sfAccountHash ((5U << 16U) + 4U)
#define sfPreviousTxnID ((5U << 16U) + 5U)
#define sfLedgerIndex ((5U << 16U) + 6U)
#define sfWalletLocator ((5U << 16U) + 7U)
#define sfRootIndex ((5U << 16U) + 8U)
#define sfAccountTxnID ((5U << 16U) + 9U)
#define sfNFTokenID ((5U << 16U) + 10U)
#define sfEmitParentTxnID ((5U << 16U) + 11U)
#define sfEmitNonce ((5U << 16U) + 12U)
#define sfEmitHookHash ((5U << 16U) + 13U)
#define sfBookDirectory ((5U << 16U) + 16U)
#define sfInvoiceID ((5U << 16U) + 17U)
#define sfNickname ((5U << 16U) + 18U)
#define sfAmendment ((5U << 16U) + 19U)
#define sfHookOn ((5U << 16U) + 20U)
#define sfDigest ((5U << 16U) + 21U)
#define sfChannel ((5U << 16U) + 22U)
#define sfConsensusHash ((5U << 16U) + 23U)
#define sfCheckID ((5U << 16U) + 24U)
#define sfValidatedHash ((5U << 16U) + 25U)
#define sfPreviousPageMin ((5U << 16U) + 26U)
#define sfNextPageMin ((5U << 16U) + 27U)
#define sfNFTokenBuyOffer ((5U << 16U) + 28U)
#define sfNFTokenSellOffer ((5U << 16U) + 29U)
#define sfHookStateKey ((5U << 16U) + 30U)
#define sfHookHash ((5U << 16U) + 31U)
#define sfHookNamespace ((5U << 16U) + 32U)
#define sfHookSetTxnID ((5U << 16U) + 33U)
#define sfOfferID ((5U << 16U) + 34U)
#define sfEscrowID ((5U << 16U) + 35U)
#define sfURITokenID ((5U << 16U) + 36U)
#define sfAmount ((6U << 16U) + 1U)
#define sfBalance ((6U << 16U) + 2U)
#define sfLimitAmount ((6U << 16U) + 3U)
#define sfTakerPays ((6U << 16U) + 4U)
#define sfTakerGets ((6U << 16U) + 5U)
#define sfLowLimit ((6U << 16U) + 6U)
#define sfHighLimit ((6U << 16U) + 7U)
#define sfFee ((6U << 16U) + 8U)
#define sfSendMax ((6U << 16U) + 9U)
#define sfDeliverMin ((6U << 16U) + 10U)
#define sfMinimumOffer ((6U << 16U) + 16U)
#define sfRippleEscrow ((6U << 16U) + 17U)
#define sfDeliveredAmount ((6U << 16U) + 18U)
#define sfNFTokenBrokerFee ((6U << 16U) + 19U)
#define sfHookCallbackFee ((6U << 16U) + 20U)
#define sfLockedBalance ((6U << 16U) + 21U)
#define sfPublicKey ((7U << 16U) + 1U)
#define sfMessageKey ((7U << 16U) + 2U)
#define sfSigningPubKey ((7U << 16U) + 3U)
#define sfTxnSignature ((7U << 16U) + 4U)
#define sfURI ((7U << 16U) + 5U)
#define sfSignature ((7U << 16U) + 6U)
#define sfDomain ((7U << 16U) + 7U)
#define sfFundCode ((7U << 16U) + 8U)
#define sfRemoveCode ((7U << 16U) + 9U)
#define sfExpireCode ((7U << 16U) + 10U)
#define sfCreateCode ((7U << 16U) + 11U)
#define sfMemoType ((7U << 16U) + 12U)
#define sfMemoData ((7U << 16U) + 13U)
#define sfMemoFormat ((7U << 16U) + 14U)
#define sfFulfillment ((7U << 16U) + 16U)
#define sfCondition ((7U << 16U) + 17U)
#define sfMasterSignature ((7U << 16U) + 18U)
#define sfUNLModifyValidator ((7U << 16U) + 19U)
#define sfValidatorToDisable ((7U << 16U) + 20U)
#define sfValidatorToReEnable ((7U << 16U) + 21U)
#define sfHookStateData ((7U << 16U) + 22U)
#define sfHookReturnString ((7U << 16U) + 23U)
#define sfHookParameterName ((7U << 16U) + 24U)
#define sfHookParameterValue ((7U << 16U) + 25U)
#define sfBlob ((7U << 16U) + 26U)
#define sfAccount ((8U << 16U) + 1U)
#define sfOwner ((8U << 16U) + 2U)
#define sfDestination ((8U << 16U) + 3U)
#define sfIssuer ((8U << 16U) + 4U)
#define sfAuthorize ((8U << 16U) + 5U)
#define sfUnauthorize ((8U << 16U) + 6U)
#define sfRegularKey ((8U << 16U) + 8U)
#define sfNFTokenMinter ((8U << 16U) + 9U)
#define sfEmitCallback ((8U << 16U) + 10U)
#define sfHookAccount ((8U << 16U) + 16U)
#define sfIndexes ((19U << 16U) + 1U)
#define sfHashes ((19U << 16U) + 2U)
#define sfAmendments ((19U << 16U) + 3U)
#define sfNFTokenOffers ((19U << 16U) + 4U)
#define sfHookNamespaces ((19U << 16U) + 5U)
#define sfPaths ((18U << 16U) + 1U)
#define sfTransactionMetaData ((14U << 16U) + 2U)
#define sfCreatedNode ((14U << 16U) + 3U)
#define sfDeletedNode ((14U << 16U) + 4U)
#define sfModifiedNode ((14U << 16U) + 5U)
#define sfPreviousFields ((14U << 16U) + 6U)
#define sfFinalFields ((14U << 16U) + 7U)
#define sfNewFields ((14U << 16U) + 8U)
#define sfTemplateEntry ((14U << 16U) + 9U)
#define sfMemo ((14U << 16U) + 10U)
#define sfSignerEntry ((14U << 16U) + 11U)
#define sfNFToken ((14U << 16U) + 12U)
#define sfEmitDetails ((14U << 16U) + 13U)
#define sfHook ((14U << 16U) + 14U)
#define sfSigner ((14U << 16U) + 16U)
#define sfMajority ((14U << 16U) + 18U)
#define sfDisabledValidator ((14U << 16U) + 19U)
#define sfEmittedTxn ((14U << 16U) + 20U)
#define sfHookExecution ((14U << 16U) + 21U)
#define sfHookDefinition ((14U << 16U) + 22U)
#define sfHookParameter ((14U << 16U) + 23U)
#define sfHookGrant ((14U << 16U) + 24U)
#define sfSigners ((15U << 16U) + 3U)
#define sfSignerEntries ((15U << 16U) + 4U)
#define sfTemplate ((15U << 16U) + 5U)
#define sfNecessary ((15U << 16U) + 6U)
#define sfSufficient ((15U << 16U) + 7U)
#define sfAffectedNodes ((15U << 16U) + 8U)
#define sfMemos ((15U << 16U) + 9U)
#define sfNFTokens ((15U << 16U) + 10U)
#define sfHooks ((15U << 16U) + 11U)
#define sfMajorities ((15U << 16U) + 16U)
#define sfDisabledValidators ((15U << 16U) + 17U)
#define sfHookExecutions ((15U << 16U) + 18U)
#define sfHookParameters ((15U << 16U) + 19U)
#define sfHookGrants ((15U << 16U) + 20U)
import { XrplClient } from "https://esm.sh/xrpl-client?bundle";
const lib = require("xrpl-accountlib");
const keypairs = require("ripple-keypairs");
/**
* @input {Account} notary_account Notary Account
* @input {Account.secret} approver_secret Approver Account
* @input invoice_id Invoice Id
*/
const { notary_account, approver_secret, invoice_id } = process.env
const approver_keypair = lib.derive.familySeed(approver_secret);
const approver_account = keypairs.deriveAddress(approver_keypair.keypair.publicKey);
const client = new XrplClient('wss://hooks-testnet-v3.xrpl-labs.com');
const main = async (proposal) => {
console.log("proposal", proposal);
try {
const { account_data } = await client.send({ command: 'account_info', 'account': approver_account });
if (!account_data) {
console.log('Approver account not found.');
client.close();
return;
}
console.log("sequence", account_data.Sequence);
const tx = {
Account: approver_account,
TransactionType: "Payment",
Amount: "1",
Destination: notary_account,
Fee: "1000000",
InvoiceID: proposal,
Sequence: account_data.Sequence,
NetworkID: "21338"
};
const {signedTransaction} = lib.sign(tx, approver_keypair);
const submit = await client.send({ command: 'submit', 'tx_blob': signedTransaction });
console.log(submit);
} catch (err) {
error(err)
}
console.log('Shutting down...');
client.close();
};
main(invoice_id);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment