import { accept, emit, etxn_details, etxn_fee_base, etxn_reserve, hook_account, ledger_seq, otxn_field, rollback, trace_, trace_num, util_accid, _g } from "./lib"; const sfAmount = ((6 << 16) + 1); const sfAccount = ((8 << 16) + 1); // for !HAS_CALLBACK const PREPARE_PAYMENT_SIMPLE_SIZE = 248; const ttPAYMENT: u8 = 0; const tfCANONICAL: u32 = 0x80000000; const atACCOUNT: u8 = 1; const atDESTINATION: u8 = 3; const amAMOUNT: u8 = 1; const amFEE: u8 = 8; @inline function TRACESTR(text: string): void { trace_("", 0, text, text.length * 2, 0) } @inline function IS_BUFFER_EQUAL(buf1: i32, buf2: i32, len: i32, gid: i32): bool { for (var i = 0; _g(gid, len + 1), i < len; ++i) { if (load(buf1 + i) != load(buf2 + i)) { return false; } } return true; } @inline function AMOUNT_TO_DROPS(amount_buffer: u32): i64 { return (load(amount_buffer) >> 7) ? -2 : ( (((load(amount_buffer) as u64) & 0xb00111111) << 56) + ((load(amount_buffer + 1) as u64) << 48) + ((load(amount_buffer + 2) as u64) << 40) + ((load(amount_buffer + 3) as u64)) << 32) + ((load(amount_buffer + 4) as u64) << 24) + ((load(amount_buffer + 5) as u64) << 16) + ((load(amount_buffer + 6) as u64) << 8) + ((load(amount_buffer + 7) as u64)); } @inline function ENCODE_TT(buf: u32, utt: u8): u32 { store(buf, 0x12); store(buf + 1, 0); store(buf + 2, utt); return buf + 3; } @inline function ENCODE_UINT32_COMMON(buf: u32, ui: u32, uf: u8): u32 { store(buf, 0x20 + (uf & 0x0F)); store(buf + 1, (ui >> 24) & 0xFF); store(buf + 2, (ui >> 16) & 0xFF); store(buf + 3, (ui >> 8) & 0xFF); store(buf + 4, ui & 0xFF); return buf + 5; } @inline function ENCODE_UINT32_UNCOMMON(buf: u32, ui: u32, uf: u8): u32 { store(buf, 0x20); store(buf + 1, uf); store(buf + 2, (ui >> 24) & 0xFF); store(buf + 3, (ui >> 16) & 0xFF); store(buf + 4, (ui >> 8) & 0xFF); store(buf + 5, ui & 0xFF); return buf + 6; } @inline function ENCODE_DROPS(buf: u32, udrops: u64, uat: u8): u32 { store(buf, 0x60 + (uat & 0x0F)); store(buf + 1, 0x40 + ((udrops >> 56) & 0x3F)); store(buf + 2, (udrops >> 48) & 0xFF); store(buf + 3, (udrops >> 40) & 0xFF); store(buf + 4, (udrops >> 32) & 0xFF); store(buf + 5, (udrops >> 24) & 0xFF); store(buf + 6, (udrops >> 16) & 0xFF); store(buf + 7, (udrops >> 8) & 0xFF); store(buf + 8, udrops & 0xFF); return buf + 9; } @inline function ENCODE_ACCOUNT(buf: u32, account_id: u32, uat: u8): u32 { store(buf, 0x80 + uat); store(buf + 1, 0x14); store(buf + 2, load(account_id)); store(buf + 10, load(account_id + 8)); store(buf + 18, load(account_id + 16)); return buf + 22; } @inline function _01_02_ENCODE_TT(buf: u32, tt: u8): u32 { return ENCODE_TT(buf, tt); } @inline function _02_02_ENCODE_FLAGS(buf: u32, tag: u32): u32 { return ENCODE_UINT32_COMMON(buf, tag, 0x2); } @inline function _02_03_ENCODE_TAG_SRC(buf: u32, tag: u32): u32 { return ENCODE_UINT32_COMMON(buf, tag, 0x3); } @inline function _02_04_ENCODE_SEQUENCE(buf: u32, sequence: u32): u32 { return ENCODE_UINT32_COMMON(buf, sequence, 0x4); } @inline function _02_14_ENCODE_TAG_DST(buf: u32, tag: u32): u32 { return ENCODE_UINT32_COMMON(buf, tag, 0xE); } @inline function _02_26_ENCODE_FLS(buf: u32, fls: u32): u32 { return ENCODE_UINT32_UNCOMMON(buf, fls, 0x1A); } @inline function _02_27_ENCODE_LLS(buf: u32, lls: u32): u32 { return ENCODE_UINT32_UNCOMMON(buf, lls, 0x1B); } @inline function _06_01_ENCODE_DROPS_AMOUNT(buf: u32, drops: u64): u32 { return ENCODE_DROPS(buf, drops, amAMOUNT); } @inline function _06_08_ENCODE_DROPS_FEE(buf: u32, drops: u64): u32 { return ENCODE_DROPS(buf, drops, amFEE); } // bug-for-bug compatible w/ ENCODE_SIGNING_PUBKEY_NULL in macro.h @inline function _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf: u32): u32 { store(buf, 0x73); store(buf + 1, 0x21); store(buf + 2, 0); store(buf + 10, 0); store(buf + 18, 0); store(buf + 25, 0); return buf + 35; } @inline function _08_01_ENCODE_ACCOUNT_SRC(buf: u32, account_id: u32): u32 { return ENCODE_ACCOUNT(buf, account_id, atACCOUNT); } @inline function _08_03_ENCODE_ACCOUNT_DST(buf: u32, account_id: u32): u32 { return ENCODE_ACCOUNT(buf, account_id, atDESTINATION); } @inline function PREPARE_PAYMENT_SIMPLE(buf_out_master: u32, drops_amount: u64, to_address: u32, dest_tag: u32, src_tag: u32): void { var buf_out = buf_out_master; const acc = memory.data(20) as u32; const cls = ledger_seq() as u32; hook_account(acc, 20); buf_out = _01_02_ENCODE_TT(buf_out, ttPAYMENT); buf_out = _02_02_ENCODE_FLAGS(buf_out, tfCANONICAL); buf_out = _02_03_ENCODE_TAG_SRC(buf_out, src_tag); buf_out = _02_04_ENCODE_SEQUENCE(buf_out, 0); buf_out = _02_14_ENCODE_TAG_DST(buf_out, dest_tag); buf_out = _02_26_ENCODE_FLS(buf_out, cls + 1); buf_out = _02_27_ENCODE_LLS(buf_out, cls + 5); buf_out = _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops_amount); var fee_ptr = buf_out; buf_out = _06_08_ENCODE_DROPS_FEE(buf_out, 0); buf_out = _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out); buf_out = _08_01_ENCODE_ACCOUNT_SRC(buf_out, acc); buf_out = _08_03_ENCODE_ACCOUNT_DST(buf_out, to_address); etxn_details(buf_out, PREPARE_PAYMENT_SIMPLE_SIZE); var fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); _06_08_ENCODE_DROPS_FEE(fee_ptr, fee); } export function hook(reserved: i32 = 0): i64 { // before we start calling hook-api functions we should tell the hook how many tx we intend to create etxn_reserve(1); // we are going to emit 1 transaction const carbon_accid = memory.data(20) as u32; const raddr = memory.data(34) as u32; const acct = "rhJGnHbgxfu7aYUwdDR8FQLFn1yTm1i2Zm"; for (var i = 0; _g(18, 35), i < 34; ++i) store(raddr + i, acct.charCodeAt(i)); const ret = util_accid( carbon_accid, 20, raddr, 34); trace_num("ret", 6, ret); // this api fetches the AccountID of the account the hook currently executing is installed on // since hooks can be triggered by both incoming and ougoing transactions this is important to know const hook_accid = memory.data(20) as u32; hook_account(hook_accid, 20); // next fetch the sfAccount field from the originating transaction const account_field = memory.data(20) as u32; const account_field_len = otxn_field(account_field, 20, sfAccount); trace_num("account_field_len", 34, account_field_len); if (account_field_len < 20) // negative values indicate errors from every api rollback("Carbon: sfAccount field missing!!!", 68, 1); // 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 if (!IS_BUFFER_EQUAL(hook_accid, account_field, 20, 50)) { // if the accounts are not equal (memcmp != 0) the otxn was sent to the hook account by someone else // accept() it and end the hook execution here accept("Carbon: Incoming transaction", 56, 2); } // execution to here means the user has sent a valid transaction FROM the account the hook is installed on // fetch the sent Amount // Amounts can be 384 bits or 64 bits. If the Amount is an XRP value it will be 64 bits. const amount_buffer = memory.data(48) as u32; const amount_len = otxn_field(amount_buffer, 48, sfAmount); var drops_to_send: i64 = 1000; // this will be the default if (amount_len != 8) { // you can trace the behaviour of your hook using the trace(buf, size, as_hex) api // which will output to xrpld's trace log TRACESTR("Carbon: Non-xrp transaction detected, sending default 1000 drops to rfCarbon"); } else { TRACESTR("Carbon: XRP transaction detected, computing 1% to send to rfCarbon"); const otxn_drops = AMOUNT_TO_DROPS(amount_buffer); trace_num("otxn_drops", 20, otxn_drops); if (otxn_drops > 100000) // if its less we send the default amount. or if there was an error we send default drops_to_send = otxn_drops / 100; // otherwise we send 1% } trace_num("drops_to_send", 26, drops_to_send); // create a buffer to write the emitted transaction into const tx = memory.data(PREPARE_PAYMENT_SIMPLE_SIZE) as u32; // this will populate the buffer with a serialized binary transaction PREPARE_PAYMENT_SIMPLE(tx, drops_to_send, carbon_accid, 0, 0); // emit the transaction const emithash = memory.data(32) as u32; const emit_result = emit(emithash, 32, tx, PREPARE_PAYMENT_SIMPLE_SIZE); trace_num("emit_result", 22, emit_result); // accept and allow the original transaction through accept("Carbon: Emitted transaction", 58, 0); return 0; }