diff --git a/packages/cosmic-proto/src/address-hooks.js b/packages/cosmic-proto/src/address-hooks.js index 819a38a2251..aaa119f43a1 100644 --- a/packages/cosmic-proto/src/address-hooks.js +++ b/packages/cosmic-proto/src/address-hooks.js @@ -23,7 +23,7 @@ * const decoded = decodeAddressHook(addressHook); * // { * // baseAddress: 'agoric1qqp0e5ys', - * // query: [Object: null prototype] { foo: [ 'bar', 'baz' ], key: 'value' } + * // query: { foo: [ 'bar', 'baz' ], key: 'value' } * // } */ @@ -31,6 +31,10 @@ import { bech32 } from 'bech32'; import queryString from 'query-string'; +/* global globalThis */ +/** @type {(x: T) => T} */ +const harden = globalThis.harden || Object.freeze; + // ADDRESS_HOOK_VERSION is the version of the address hook format used in this // module. const ADDRESS_HOOK_VERSION = 0; @@ -46,6 +50,7 @@ const ADDRESS_HOOK_BYTE_PREFIX = [ 0xf1, 0x70, // | ADDRESS_HOOK_VERSION ]; +harden(ADDRESS_HOOK_BYTE_PREFIX); /** * The default maximum number of characters in a bech32-encoded hooked address. @@ -63,6 +68,9 @@ export const DEFAULT_HOOKED_ADDRESS_CHAR_LIMIT = 1024; * { key: ['value1', null, 'value3'] } // '?key=value1&key&key=value3' */ +/** + * How many bytes are used to store the length of the base address. + */ export const BASE_ADDRESS_LENGTH_BYTES = 2; /** @@ -78,8 +86,9 @@ export const decodeBech32 = ( const rawBytes = bech32.fromWords(words); const bytes = new Uint8Array(rawBytes); - return { prefix, bytes }; + return harden({ prefix, bytes }); }; +harden(decodeBech32); /** * @param {string} humanReadablePart @@ -95,6 +104,7 @@ export const encodeBech32 = ( const words = bech32.toWords(bytes); return bech32.encode(humanReadablePart, words, charLimit); }; +harden(encodeBech32); /** * Join raw base address bytes and hook data into a bech32-encoded hooked @@ -162,6 +172,7 @@ export const joinHookedAddress = ( return encodeBech32(prefix, hookBuf, charLimit); }; +harden(joinHookedAddress); /** * @param {string} baseAddress @@ -175,6 +186,7 @@ export const encodeAddressHook = (baseAddress, query, charLimit) => { const hookData = te.encode(`?${queryStr}`); return joinHookedAddress(baseAddress, hookData, charLimit); }; +harden(encodeAddressHook); /** * @param {string} addressHook @@ -188,10 +200,15 @@ export const decodeAddressHook = (addressHook, charLimit) => { throw Error(`Hook data does not start with '?': ${hookStr}`); } - /** @type {HookQuery} */ - const query = queryString.parse(hookStr); - return { baseAddress, query }; + const parsedQuery = queryString.parse(hookStr); + + /** + * @type {HookQuery} + */ + const query = harden({ ...parsedQuery }); + return harden({ baseAddress, query }); }; +harden(decodeAddressHook); /** * @param {string} specimen @@ -214,7 +231,7 @@ export const splitHookedAddress = ( maybeMagicByte &= 0xf0; } if (maybeMagicByte !== ADDRESS_HOOK_BYTE_PREFIX[i]) { - return { baseAddress: specimen, hookData: new Uint8Array() }; + return harden({ baseAddress: specimen, hookData: new Uint8Array() }); } } @@ -246,5 +263,6 @@ export const splitHookedAddress = ( const hookData = bytes.subarray(prefixLength + b, -BASE_ADDRESS_LENGTH_BYTES); - return { baseAddress, hookData }; + return harden({ baseAddress, hookData }); }; +harden(splitHookedAddress);