mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-21 04:35:49 +00:00
X-address compatibility (#90)
ripple-binary-codec can encode transactions with X-Addresses
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { decodeAccountID, encodeAccountID } from "ripple-address-codec";
|
||||
import { Hash160 } from "./hash-160";
|
||||
|
||||
const HEX_REGEX = /^[A-F0-9]{40}$/;
|
||||
|
||||
/**
|
||||
* Class defining how to encode and decode an AccountID
|
||||
*/
|
||||
@@ -27,9 +29,9 @@ class AccountID extends Hash160 {
|
||||
return new AccountID();
|
||||
}
|
||||
|
||||
return /^r/.test(value)
|
||||
? this.fromBase58(value)
|
||||
: new AccountID(Buffer.from(value, "hex"));
|
||||
return HEX_REGEX.test(value)
|
||||
? new AccountID(Buffer.from(value, "hex"))
|
||||
: this.fromBase58(value);
|
||||
}
|
||||
|
||||
throw new Error("Cannot construct AccountID from value given");
|
||||
|
||||
@@ -1,11 +1,53 @@
|
||||
import { Field, FieldInstance } from "../enums";
|
||||
import { SerializedType, JsonObject } from "./serialized-type";
|
||||
import {
|
||||
xAddressToClassicAddress,
|
||||
isValidXAddress,
|
||||
} from "ripple-address-codec";
|
||||
import { BinaryParser } from "../serdes/binary-parser";
|
||||
import { BinarySerializer, BytesList } from "../serdes/binary-serializer";
|
||||
|
||||
const OBJECT_END_MARKER = Buffer.from([0xe1]);
|
||||
const OBJECT_END_MARKER_NAME = "ObjectEndMarker";
|
||||
const OBJECT_FIELD_TYPE_NAME = "STObject";
|
||||
const OBJECT_END_MARKER_BYTE = Buffer.from([0xe1]);
|
||||
const OBJECT_END_MARKER = "ObjectEndMarker";
|
||||
const ST_OBJECT = "STObject";
|
||||
const DESTINATION = "Destination";
|
||||
const ACCOUNT = "Account";
|
||||
const SOURCE_TAG = "SourceTag";
|
||||
const DEST_TAG = "DestinationTag";
|
||||
|
||||
/**
|
||||
* Break down an X-Address into an account and a tag
|
||||
*
|
||||
* @param field Name of field
|
||||
* @param xAddress X-Address corresponding to the field
|
||||
*/
|
||||
function handleXAddress(field: string, xAddress: string): JsonObject {
|
||||
const decoded = xAddressToClassicAddress(xAddress);
|
||||
|
||||
let tagName;
|
||||
if (field === DESTINATION) tagName = DEST_TAG;
|
||||
else if (field === ACCOUNT) tagName = SOURCE_TAG;
|
||||
else if (decoded.tag !== false)
|
||||
throw new Error(`${field} cannot have an associated tag`);
|
||||
|
||||
return decoded.tag !== false
|
||||
? { [field]: decoded.classicAddress, [tagName]: decoded.tag }
|
||||
: { [field]: decoded.classicAddress };
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that two objects don't both have the same tag fields
|
||||
*
|
||||
* @param obj1 First object to check for tags
|
||||
* @param obj2 Second object to check for tags
|
||||
* @throws When both objects have SourceTag or DestinationTag
|
||||
*/
|
||||
function checkForDuplicateTags(obj1: JsonObject, obj2: JsonObject): void {
|
||||
if (!(obj1[SOURCE_TAG] === undefined || obj2[SOURCE_TAG] === undefined))
|
||||
throw new Error("Cannot have Account X-Address and SourceTag");
|
||||
if (!(obj1[DEST_TAG] === undefined || obj2[DEST_TAG] === undefined))
|
||||
throw new Error("Cannot have Destination X-Address and DestinationTag");
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for Serializing/Deserializing objects
|
||||
@@ -23,15 +65,15 @@ class STObject extends SerializedType {
|
||||
|
||||
while (!parser.end()) {
|
||||
const field = parser.readField();
|
||||
if (field.name === OBJECT_END_MARKER_NAME) {
|
||||
if (field.name === OBJECT_END_MARKER) {
|
||||
break;
|
||||
}
|
||||
|
||||
const associatedValue = parser.readFieldValue(field);
|
||||
|
||||
bytes.writeFieldAndValue(field, associatedValue);
|
||||
if (field.type.name === OBJECT_FIELD_TYPE_NAME) {
|
||||
bytes.put(OBJECT_END_MARKER);
|
||||
if (field.type.name === ST_OBJECT) {
|
||||
bytes.put(OBJECT_END_MARKER_BYTE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +98,16 @@ class STObject extends SerializedType {
|
||||
const list: BytesList = new BytesList();
|
||||
const bytes: BinarySerializer = new BinarySerializer(list);
|
||||
|
||||
let sorted = Object.keys(value)
|
||||
const xAddressDecoded = Object.entries(value).reduce((acc, [key, val]) => {
|
||||
let handled: JsonObject | undefined = undefined;
|
||||
if (isValidXAddress(val)) {
|
||||
handled = handleXAddress(key, val);
|
||||
checkForDuplicateTags(handled, value as JsonObject);
|
||||
}
|
||||
return Object.assign(acc, handled ?? { [key]: val });
|
||||
}, {});
|
||||
|
||||
let sorted = Object.keys(xAddressDecoded)
|
||||
.map((f: string): FieldInstance => Field[f] as FieldInstance)
|
||||
.filter((f: FieldInstance): boolean => f !== undefined && f.isSerialized)
|
||||
.sort((a, b) => {
|
||||
@@ -68,11 +119,13 @@ class STObject extends SerializedType {
|
||||
}
|
||||
|
||||
sorted.forEach((field) => {
|
||||
const associatedValue = field.associatedType.from(value[field.name]);
|
||||
const associatedValue = field.associatedType.from(
|
||||
xAddressDecoded[field.name]
|
||||
);
|
||||
|
||||
bytes.writeFieldAndValue(field, associatedValue);
|
||||
if (field.type.name === OBJECT_FIELD_TYPE_NAME) {
|
||||
bytes.put(OBJECT_END_MARKER);
|
||||
if (field.type.name === ST_OBJECT) {
|
||||
bytes.put(OBJECT_END_MARKER_BYTE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -90,7 +143,7 @@ class STObject extends SerializedType {
|
||||
|
||||
while (!objectParser.end()) {
|
||||
const field = objectParser.readField();
|
||||
if (field.name === OBJECT_END_MARKER_NAME) {
|
||||
if (field.name === OBJECT_END_MARKER) {
|
||||
break;
|
||||
}
|
||||
accumulator[field.name] = objectParser.readFieldValue(field).toJSON();
|
||||
|
||||
Reference in New Issue
Block a user