Skip to content

Commit

Permalink
feat!: Make serialize methods return hex-encoded strings, instead of …
Browse files Browse the repository at this point in the history
…bytes
  • Loading branch information
janniks committed Mar 12, 2024
1 parent e2574df commit 6a1fff8
Show file tree
Hide file tree
Showing 23 changed files with 411 additions and 251 deletions.
4 changes: 3 additions & 1 deletion .github/.husky/commitlint.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"header-max-length": [0, "always"],
"scope-max-length": [0, "always"],
"subject-max-length": [0, "always"],
"type-max-length": [0, "always"]
"type-max-length": [0, "always"],

"subject-case": [0, "always", "sentence-case"]
}
}
7 changes: 7 additions & 0 deletions .github/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [StacksNodeApi](#stacksnodeapi)
- [StacksNetwork to StacksNodeApi](#stacksnetwork-to-stacksnodeapi)
- [Clarity Representation](#clarity-representation)
- [`serialize` methods](#serialize-methods)
- [Stacks.js (\<=4.x.x) → (5.x.x)](#stacksjs-4xx--5xx)
- [Breaking Changes](#breaking-changes-1)
- [Buffer to Uint8Array](#buffer-to-uint8array)
Expand All @@ -26,6 +27,8 @@

- The `@stacks/network` `new StacksNetwork()` objects were removed. Instead `@stacks/network` now exports the objects `STACKS_MAINNET`, `STACKS_TESNET`, and `STACKS_DEVNET`, which are static (and shouldn't be changed for most use-cases). [Read more...](#stacks-network)
- The `ClarityType` enum was replaced by a readable version. The previous (wire format compatible) enum is still available as `ClarityWireType`. [Read more...](#clarity-representation)
- The `serializeXyz` methods were changed to return `string` (hex-encoded) instead of `Uint8Array`. Compatible `serializeXzyBytes` methods were added to ease the migration. [Read more...](#serialize-methods)
- The `AssetInfo` type was renamed to `Asset` for accuracy.

### Stacks Network

Expand Down Expand Up @@ -144,6 +147,10 @@ For `bigint` values, the type of the `value` property is a now `string`, for bet
}
```

### `serialize` methods

- `serialize` ... todo

## Stacks.js (&lt;=4.x.x) → (5.x.x)

### Breaking Changes
Expand Down
12 changes: 6 additions & 6 deletions packages/transactions/src/authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ import {
} from './keys';
import {
deserializeMessageSignature,
serializeMessageSignature,
serializeMessageSignatureBytes,
TransactionAuthField,
} from './signature';
import {
addressFromPublicKeys,
createEmptyAddress,
createLPList,
deserializeLPList,
serializeLPList,
deserializeLPListBytes,
serializeLPListBytes,
} from './types';
import { cloneDeep, leftPadHex, txidFromData } from './utils';

Expand Down Expand Up @@ -205,7 +205,7 @@ export function serializeSingleSigSpendingCondition(
intToBytes(condition.nonce, false, 8),
intToBytes(condition.fee, false, 8),
condition.keyEncoding as number,
serializeMessageSignature(condition.signature),
serializeMessageSignatureBytes(condition.signature),
];
return concatArray(bytesArray);
}
Expand All @@ -221,7 +221,7 @@ export function serializeMultiSigSpendingCondition(
];

const fields = createLPList(condition.fields);
bytesArray.push(serializeLPList(fields));
bytesArray.push(serializeLPListBytes(fields));

const numSigs = new Uint8Array(2);
writeUInt16BE(numSigs, condition.signaturesRequired, 0);
Expand Down Expand Up @@ -265,7 +265,7 @@ export function deserializeMultiSigSpendingCondition(
const nonce = BigInt('0x' + bytesToHex(bytesReader.readBytes(8)));
const fee = BigInt('0x' + bytesToHex(bytesReader.readBytes(8)));

const fields = deserializeLPList(bytesReader, StacksMessageType.TransactionAuthField)
const fields = deserializeLPListBytes(bytesReader, StacksMessageType.TransactionAuthField)
.values as TransactionAuthField[];

let haveUncompressed = false;
Expand Down
6 changes: 3 additions & 3 deletions packages/transactions/src/cl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
noneCV,
responseErrorCV,
responseOkCV,
serializeCV,
serializeCVBytes,
someCV,
standardPrincipalCV,
stringAsciiCV,
Expand Down Expand Up @@ -269,15 +269,15 @@ export const tuple = tupleCV;
/**
* `Cl.serialize` — Serializes a Clarity JS object to the equivalent hex-encoded representation
*
* Alias for {@link serializeCV}
* Alias for {@link serializeCVBytes}
* @example
* ```
* import { Cl } from '@stacks/transactions';
* Cl.serialize(Cl.uint(100));
* ```
* @see {@link deserialize}
*/
export const serialize = serializeCV;
export const serialize = serializeCVBytes;
/**
* `Cl.deserialize` — Deserializes a hex string to the equivalent Clarity JS object
*
Expand Down
15 changes: 5 additions & 10 deletions packages/transactions/src/clarity/deserialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,13 @@ import {
tupleCV,
} from '.';
import { BytesReader as BytesReader } from '../bytesReader';
import { deserializeAddress, deserializeLPString } from '../types';
import { deserializeAddressBytes, deserializeLPStringBytes } from '../types';
import { DeserializationError } from '../errors';
import { stringAsciiCV, stringUtf8CV } from './types/stringCV';
import { bytesToAscii, bytesToUtf8, hexToBytes } from '@stacks/common';

/**
* Deserializes clarity value to clarity type
*
* @param {value} Uint8Array | string value to be converted to clarity type
**
* @returns {ClarityType} returns the clarity type instance
*
* @example
* ```
* import { intCV, serializeCV, deserializeCV } from '@stacks/transactions';
Expand Down Expand Up @@ -79,12 +74,12 @@ export function deserializeCV<T extends ClarityValue = ClarityValue>(
return falseCV() as T;

case ClarityWireType.address:
const sAddress = deserializeAddress(bytesReader);
const sAddress = deserializeAddressBytes(bytesReader);
return standardPrincipalCVFromAddress(sAddress) as T;

case ClarityWireType.contract:
const cAddress = deserializeAddress(bytesReader);
const contractName = deserializeLPString(bytesReader);
const cAddress = deserializeAddressBytes(bytesReader);
const contractName = deserializeLPStringBytes(bytesReader);
return contractPrincipalCVFromAddress(cAddress, contractName) as T;

case ClarityWireType.ok:
Expand All @@ -111,7 +106,7 @@ export function deserializeCV<T extends ClarityValue = ClarityValue>(
const tupleLength = bytesReader.readUInt32BE();
const tupleContents: { [key: string]: ClarityValue } = {};
for (let i = 0; i < tupleLength; i++) {
const clarityName = deserializeLPString(bytesReader).content;
const clarityName = deserializeLPStringBytes(bytesReader).content;
if (clarityName === undefined) {
throw new DeserializationError('"content" is undefined');
}
Expand Down
5 changes: 3 additions & 2 deletions packages/transactions/src/clarity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ export {
stringAsciiCV,
stringCV,
} from './types/stringCV';
export { serializeCV } from './serialize';
export { deserializeCV } from './deserialize';

export * from './serialize';
export * from './deserialize';
33 changes: 16 additions & 17 deletions packages/transactions/src/clarity/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import {
writeUInt32BE,
utf8ToBytes,
asciiToBytes,
bytesToHex,
} from '@stacks/common';
import { serializeAddress, serializeLPString } from '../types';
import { serializeAddressBytes, serializeLPStringBytes } from '../types';
import { createLPString } from '../postcondition-types';
import {
BooleanCV,
Expand Down Expand Up @@ -40,7 +41,7 @@ function serializeOptionalCV(cv: OptionalCV): Uint8Array {
if (cv.type === ClarityType.OptionalNone) {
return new Uint8Array([clarityTypeToByte(cv.type)]);
} else {
return bytesWithTypeID(cv.type, serializeCV(cv.value));
return bytesWithTypeID(cv.type, serializeCVBytes(cv.value));
}
}

Expand All @@ -61,18 +62,18 @@ function serializeUIntCV(cv: UIntCV): Uint8Array {
}

function serializeStandardPrincipalCV(cv: StandardPrincipalCV): Uint8Array {
return bytesWithTypeID(cv.type, serializeAddress(cv.address));
return bytesWithTypeID(cv.type, serializeAddressBytes(cv.address));
}

function serializeContractPrincipalCV(cv: ContractPrincipalCV): Uint8Array {
return bytesWithTypeID(
cv.type,
concatBytes(serializeAddress(cv.address), serializeLPString(cv.contractName))
concatBytes(serializeAddressBytes(cv.address), serializeLPStringBytes(cv.contractName))
);
}

function serializeResponseCV(cv: ResponseCV) {
return bytesWithTypeID(cv.type, serializeCV(cv.value));
return bytesWithTypeID(cv.type, serializeCVBytes(cv.value));
}

function serializeListCV(cv: ListCV) {
Expand All @@ -83,7 +84,7 @@ function serializeListCV(cv: ListCV) {
bytesArray.push(length);

for (const value of cv.list) {
const serializedValue = serializeCV(value);
const serializedValue = serializeCVBytes(value);
bytesArray.push(serializedValue);
}

Expand All @@ -101,9 +102,9 @@ function serializeTupleCV(cv: TupleCV) {

for (const key of lexicographicOrder) {
const nameWithLength = createLPString(key);
bytesArray.push(serializeLPString(nameWithLength));
bytesArray.push(serializeLPStringBytes(nameWithLength));

const serializedValue = serializeCV(cv.data[key]);
const serializedValue = serializeCVBytes(cv.data[key]);
bytesArray.push(serializedValue);
}

Expand Down Expand Up @@ -132,25 +133,23 @@ function serializeStringUtf8CV(cv: StringUtf8CV) {
}

/**
* Serializes clarity value to Uint8Array
*
* @param {ClarityValue} value to be converted to bytes
**
* @returns {Uint8Array} returns the bytes
*
* Serializes clarity value to hex
* @example
* ```
* import { intCV, serializeCV } from '@stacks/transactions';
*
* const serialized = serializeCV(intCV(100)); // Similarly works for other clarity types as well like listCV, booleanCV ...
*
* // <Uint8Array 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64>
* // '0000000000000000000000000000000064'
* ```
*
* @see
* {@link https://github.com/hirosystems/stacks.js/blob/main/packages/transactions/tests/clarity.test.ts | clarity test cases for more examples}
*/
export function serializeCV(value: ClarityValue): Uint8Array {
export function serializeCV(value: ClarityValue): string {
return bytesToHex(serializeCVBytes(value));
}
/** @ignore */
export function serializeCVBytes(value: ClarityValue): Uint8Array {
switch (value.type) {
case ClarityType.BoolTrue:
case ClarityType.BoolFalse:
Expand Down
2 changes: 1 addition & 1 deletion packages/transactions/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export enum StacksMessageType {
Principal,
LengthPrefixedString,
MemoString,
AssetInfo,
Asset,
PostCondition,
PublicKey,
LengthPrefixedList,
Expand Down
12 changes: 6 additions & 6 deletions packages/transactions/src/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ import {
validateHash256,
with0x,
} from '@stacks/common';
import { deriveDefaultUrl } from '@stacks/network';
import { ClarityValue, NoneCV, deserializeCV, serializeCV } from './clarity';
import { ClarityAbi } from './contract-abi';
import { NoEstimateAvailableError } from './errors';
import { serializePayload } from './payload';
import { serializePayloadBytes } from './payload';
import {
StacksTransaction,
deriveNetworkFromTx,
estimateTransactionByteLength,
} from './transaction';
import { cvToHex, defaultApiFromNetwork, parseReadOnlyResponse } from './utils';
import {
FeeEstimateResponse,
FeeEstimation,
TxBroadcastResult,
TxBroadcastResultOk,
TxBroadcastResultRejected,
} from './types';
import { deriveDefaultUrl } from '@stacks/network';
import { ClarityAbi } from './contract-abi';
import { cvToHex, defaultApiFromNetwork, parseReadOnlyResponse } from './utils';

export const BROADCAST_PATH = '/v2/transactions';
export const TRANSFER_FEE_ESTIMATE_PATH = '/v2/fees/transfer';
Expand Down Expand Up @@ -227,7 +227,7 @@ export async function estimateFee({
const estimatedLength = estimateTransactionByteLength(txOpt);
return (
await estimateTransaction({
payload: bytesToHex(serializePayload(txOpt.payload)),
payload: bytesToHex(serializePayloadBytes(txOpt.payload)),
estimatedLength,
api,
})
Expand Down Expand Up @@ -344,7 +344,7 @@ export async function getContractMapEntry<T extends ClarityValue = ClarityValue>
mapName: string;
mapKey: ClarityValue;
} & ApiParam): Promise<T | NoneCV> {
const keyHex = with0x(bytesToHex(serializeCV(mapKey)));
const keyHex = with0x(serializeCV(mapKey));

const options = {
method: 'POST',
Expand Down
2 changes: 1 addition & 1 deletion packages/transactions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export {
isPoisonPayload,
isSmartContractPayload,
isTokenTransferPayload,
serializePayload,
serializePayloadBytes as serializePayload,
} from './payload';
/**
* ### `Pc.` Post Condition Builder
Expand Down
16 changes: 14 additions & 2 deletions packages/transactions/src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
hexToBigInt,
hexToBytes,
intToHex,
isInstance,
parseRecoverableSignatureVrs,
PRIVATE_KEY_COMPRESSED_LENGTH,
privateKeyToBytes,
Expand Down Expand Up @@ -128,7 +129,11 @@ export function publicKeyIsCompressed(publicKey: PublicKey): boolean {
return !publicKeyToHex(publicKey).startsWith('04');
}

export function serializePublicKey(key: StacksPublicKey): Uint8Array {
export function serializePublicKey(key: StacksPublicKey): string {
return bytesToHex(serializePublicKeyBytes(key));
}
/** @ignore */
export function serializePublicKeyBytes(key: StacksPublicKey): Uint8Array {
return key.data.slice();
}

Expand All @@ -147,7 +152,14 @@ export function compressPublicKey(publicKey: PublicKey): string {
return Point.fromHex(publicKeyToHex(publicKey)).toHex(true);
}

export function deserializePublicKey(bytesReader: BytesReader): StacksPublicKey {
export function deserializePublicKey(serialized: string): StacksPublicKey {
return deserializePublicKeyBytes(hexToBytes(serialized));
}
/** @ignore */
export function deserializePublicKeyBytes(serialized: Uint8Array | BytesReader): StacksPublicKey {
const bytesReader = isInstance(serialized, BytesReader)
? serialized
: new BytesReader(serialized);
const fieldId = bytesReader.readUInt8();
const keyLength =
fieldId === 4 ? UNCOMPRESSED_PUBKEY_LENGTH_BYTES : COMPRESSED_PUBKEY_LENGTH_BYTES;
Expand Down
Loading

0 comments on commit 6a1fff8

Please sign in to comment.