From 237a4dfd99218c3b542cbb6ff858a6434236df46 Mon Sep 17 00:00:00 2001 From: solidsnakedev Date: Mon, 1 Sep 2025 16:59:06 -0600 Subject: [PATCH 1/4] feat: upgrade modules; add sdk directory --- packages/evolution/package.json | 1 + packages/evolution/src/AddressDetails.ts | 22 +- packages/evolution/src/AddressEras.ts | 313 +++++++++++++++++ packages/evolution/src/AddressStructure.ts | 282 +++++++++++++++ packages/evolution/src/Bytes.ts | 41 +-- packages/evolution/src/NetworkId.ts | 2 +- packages/evolution/src/Script.ts | 6 + packages/evolution/src/ScriptHash.ts | 52 +++ packages/evolution/src/TransactionBody.ts | 20 +- packages/evolution/src/TransactionOutput.ts | 20 +- packages/evolution/src/index.ts | 6 +- packages/evolution/src/sdk/Assets.ts | 135 +++++++ .../evolution/src/{ => sdk}/Devnet/Devnet.ts | 0 .../src/{ => sdk}/Devnet/Devnet.ts.bak | 0 .../src/{ => sdk}/Devnet/DevnetDefault.ts | 0 .../src/{ => sdk}/Devnet/DevnetDefault.ts.bak | 0 packages/evolution/src/sdk/Script.ts | 124 +++++++ packages/evolution/src/sdk/UTxO.ts | 183 ++++++++++ .../evolution/src/sdk/provider/Kupmios.ts | 332 ++++++++++++++++++ .../evolution/src/sdk/provider/Provider.ts | 31 ++ .../src/sdk/provider/internal/HttpUtils.ts | 33 ++ .../src/sdk/provider/internal/Kupo.ts | 53 +++ .../src/sdk/provider/internal/Ogmios.ts | 204 +++++++++++ packages/evolution/src/sdk/provider/types.ts | 83 +++++ packages/evolution/src/sdk/wallet/Wallet.ts | 83 +++++ packages/evolution/src/sdk/wallet/utils.ts | 83 +++++ packages/evolution/test/Script.CML.test.ts | 217 ++++++++++++ .../evolution/test/WalletFromSeed.test.ts | 94 +++++ pnpm-lock.yaml | 3 + 29 files changed, 2358 insertions(+), 65 deletions(-) create mode 100644 packages/evolution/src/AddressEras.ts create mode 100644 packages/evolution/src/AddressStructure.ts create mode 100644 packages/evolution/src/sdk/Assets.ts rename packages/evolution/src/{ => sdk}/Devnet/Devnet.ts (100%) rename packages/evolution/src/{ => sdk}/Devnet/Devnet.ts.bak (100%) rename packages/evolution/src/{ => sdk}/Devnet/DevnetDefault.ts (100%) rename packages/evolution/src/{ => sdk}/Devnet/DevnetDefault.ts.bak (100%) create mode 100644 packages/evolution/src/sdk/Script.ts create mode 100644 packages/evolution/src/sdk/UTxO.ts create mode 100644 packages/evolution/src/sdk/provider/Kupmios.ts create mode 100644 packages/evolution/src/sdk/provider/Provider.ts create mode 100644 packages/evolution/src/sdk/provider/internal/HttpUtils.ts create mode 100644 packages/evolution/src/sdk/provider/internal/Kupo.ts create mode 100644 packages/evolution/src/sdk/provider/internal/Ogmios.ts create mode 100644 packages/evolution/src/sdk/provider/types.ts create mode 100644 packages/evolution/src/sdk/wallet/Wallet.ts create mode 100644 packages/evolution/src/sdk/wallet/utils.ts create mode 100644 packages/evolution/test/Script.CML.test.ts create mode 100644 packages/evolution/test/WalletFromSeed.test.ts diff --git a/packages/evolution/package.json b/packages/evolution/package.json index 03e8f131..b7012fba 100644 --- a/packages/evolution/package.json +++ b/packages/evolution/package.json @@ -45,6 +45,7 @@ "typescript": "^5.9.2" }, "dependencies": { + "@effect/platform": "^0.90.6", "@effect/platform-node": "^0.96.0", "@noble/hashes": "^1.8.0", "@scure/base": "^1.2.6", diff --git a/packages/evolution/src/AddressDetails.ts b/packages/evolution/src/AddressDetails.ts index 4a8919ed..28bb2bc8 100644 --- a/packages/evolution/src/AddressDetails.ts +++ b/packages/evolution/src/AddressDetails.ts @@ -1,6 +1,6 @@ import { Data, Effect as Eff, ParseResult, Schema } from "effect" -import * as Address from "./Address.js" +import * as AddressEras from "./AddressEras.js" import * as Bytes from "./Bytes.js" import * as Function from "./Function.js" import * as NetworkId from "./NetworkId.js" @@ -26,7 +26,7 @@ export class AddressDetails extends Schema.Class("AddressDetails Schema.Literal("RewardAccount"), Schema.Literal("ByronAddress") ), - address: Address.Address, + address: AddressEras.AddressEras, bech32: Schema.String, hex: Bytes.HexSchema }) {} @@ -36,8 +36,8 @@ export const FromBech32 = Schema.transformOrFail(Schema.String, AddressDetails, encode: (_, __, ___, toA) => ParseResult.succeed(toA.bech32), decode: (_, __, ___, fromA) => Eff.gen(function* () { - const address = yield* ParseResult.decode(Address.FromBech32)(fromA) - const hex = yield* ParseResult.encode(Address.FromHex)(address) + const address = yield* ParseResult.decode(AddressEras.FromBech32)(fromA) + const hex = yield* ParseResult.encode(AddressEras.FromHex)(address) return new AddressDetails({ networkId: address.networkId, type: address._tag, @@ -53,8 +53,8 @@ export const FromHex = Schema.transformOrFail(Bytes.HexSchema, AddressDetails, { encode: (_, __, ___, toA) => ParseResult.succeed(toA.hex), decode: (_, __, ___, fromA) => Eff.gen(function* () { - const address = yield* ParseResult.decode(Address.FromHex)(fromA) - const bech32 = yield* ParseResult.encode(Address.FromBech32)(address) + const address = yield* ParseResult.decode(AddressEras.FromHex)(fromA) + const bech32 = yield* ParseResult.encode(AddressEras.FromBech32)(address) return new AddressDetails({ networkId: address.networkId, type: address._tag, @@ -83,7 +83,7 @@ export const equals = (self: AddressDetails, that: AddressDetails): boolean => { return ( self.networkId === that.networkId && self.type === that.type && - Address.equals(self.address, that.address) && + AddressEras.equals(self.address, that.address) && self.bech32 === that.bech32 && self.hex === that.hex ) @@ -95,7 +95,7 @@ export const equals = (self: AddressDetails, that: AddressDetails): boolean => { * @since 2.0.0 * @category arbitrary */ -export const arbitrary = Address.arbitrary.map((address) => fromAddress(address)) +export const arbitrary = AddressEras.arbitrary.map((address) => fromAddress(address)) /** * Create AddressDetails from an Address. @@ -103,10 +103,10 @@ export const arbitrary = Address.arbitrary.map((address) => fromAddress(address) * @since 2.0.0 * @category constructors */ -export const fromAddress = (address: Address.Address): AddressDetails => { +export const fromAddress = (address: AddressEras.AddressEras): AddressDetails => { // Use schema encoding to get the serialized formats - const bech32 = Eff.runSync(Schema.encode(Address.FromBech32)(address)) - const hex = Eff.runSync(Schema.encode(Address.FromHex)(address)) + const bech32 = Eff.runSync(Schema.encode(AddressEras.FromBech32)(address)) + const hex = Eff.runSync(Schema.encode(AddressEras.FromHex)(address)) return new AddressDetails({ networkId: address.networkId, type: address._tag, diff --git a/packages/evolution/src/AddressEras.ts b/packages/evolution/src/AddressEras.ts new file mode 100644 index 00000000..81138839 --- /dev/null +++ b/packages/evolution/src/AddressEras.ts @@ -0,0 +1,313 @@ +import { bech32 } from "@scure/base" +import { Data, Effect as Eff, FastCheck, ParseResult, Schema } from "effect" + +import * as BaseAddress from "./BaseAddress.js" +import * as ByronAddress from "./ByronAddress.js" +import * as Bytes from "./Bytes.js" +import * as EnterpriseAddress from "./EnterpriseAddress.js" +import * as Function from "./Function.js" +import * as PointerAddress from "./PointerAddress.js" +import * as RewardAccount from "./RewardAccount.js" + +/** + * CDDL specs + * ``` + * ; address format: + * ; [ 8 bit header | payload ]; + * ; + * ; shelley payment addresses: + * ; bit 7: 0 + * ; bit 6: base/other + * ; bit 5: pointer/enterprise [for base: stake cred is keyhash/scripthash] + * ; bit 4: payment cred is keyhash/scripthash + * ; bits 3-0: network id + * ; + * ; reward addresses: + * ; bits 7-5: 111 + * ; bit 4: credential is keyhash/scripthash + * ; bits 3-0: network id + * ; + * ; byron addresses: + * ; bits 7-4: 1000 + * ; + * ; 0000: base address: keyhash28,keyhash28 + * ; 0001: base address: scripthash28,keyhash28 + * ; 0010: base address: keyhash28,scripthash28 + * ; 0011: base address: scripthash28,scripthash28 + * ; 0100: pointer address: keyhash28, 3 variable length uint + * ; 0101: pointer address: scripthash28, 3 variable length uint + * ; 0110: enterprise address: keyhash28 + * ; 0111: enterprise address: scripthash28 + * ; 1000: byron address + * ; 1110: reward account: keyhash28 + * ; 1111: reward account: scripthash28 + * ; 1001-1101: future formats + * ``` + */ + +/** + * Error thrown when address operations fail + * + * @since 2.0.0 + * @category model + */ +export class AddressError extends Data.TaggedError("AddressError")<{ + message?: string + cause?: unknown +}> {} + +/** + * Union type representing all possible address types. + * + * @since 2.0.0 + * @category model + */ +export const AddressEras = Schema.Union( + BaseAddress.BaseAddress, + EnterpriseAddress.EnterpriseAddress, + PointerAddress.PointerAddress, + RewardAccount.RewardAccount, + ByronAddress.ByronAddress +) + +export const isAddress = Schema.is(AddressEras) + +/** + * Type representing an address. + * + * @since 2.0.0 + * @category model + */ +export type AddressEras = typeof AddressEras.Type + +/** + * Schema for encoding/decoding addresses as bytes. + * + * @since 2.0.0 + * @category schema + */ +export const FromBytes = Schema.transformOrFail(Schema.Uint8ArrayFromSelf, AddressEras, { + strict: true, + encode: (_, __, ___, toA) => { + switch (toA._tag) { + case "BaseAddress": + return ParseResult.encode(BaseAddress.FromBytes)(toA) + case "EnterpriseAddress": + return ParseResult.encode(EnterpriseAddress.FromBytes)(toA) + case "PointerAddress": + return ParseResult.encode(PointerAddress.FromBytes)(toA) + case "RewardAccount": + return ParseResult.encode(RewardAccount.FromBytes)(toA) + case "ByronAddress": + return ParseResult.encode(ByronAddress.BytesSchema)(toA) + } + }, + decode: (_, __, ast, fromA) => + Eff.gen(function* () { + const header = fromA[0] + // Extract address type from the upper 4 bits (bits 4-7) + const addressType = header >> 4 + + switch (addressType) { + // Base address types (0000, 0001, 0010, 0011) + // Format: [payment credential, stake credential] + case 0b0000: // Key payment, Key stake + case 0b0001: // Script payment, Key stake + case 0b0010: // Key payment, Script stake + case 0b0011: + return yield* ParseResult.decode(BaseAddress.FromBytes)(fromA) + + // Enterprise address types (0110, 0111) + // Format: [payment credential only] + case 0b0110: // Key payment + case 0b0111: + return yield* ParseResult.decode(EnterpriseAddress.FromBytes)(fromA) + + // Pointer address types (0100, 0101) + // Format: [payment credential, variable length integers for slot, txIndex, certIndex] + case 0b0100: // Key payment with pointer + case 0b0101: + return yield* ParseResult.decode(PointerAddress.FromBytes)(fromA) + + case 0b1110: + case 0b1111: + return yield* ParseResult.decode(RewardAccount.FromBytes)(fromA) + + case 0b1000: + return yield* ParseResult.decode(ByronAddress.BytesSchema)(fromA) + + default: + return yield* ParseResult.fail(new ParseResult.Type(ast, fromA, `Unknown address type: ${addressType}`)) + } + }) +}) + +/** + * Schema for encoding/decoding addresses as hex strings. + * + * @since 2.0.0 + * @category schema + */ +export const FromHex = Schema.compose(Bytes.FromHex, FromBytes) + +/** + * Schema for encoding/decoding addresses as Bech32 strings. + * + * @since 2.0.0 + * @category schema + */ +export const FromBech32 = Schema.transformOrFail(Schema.String, AddressEras, { + strict: true, + encode: (_, __, ast, toA) => + Eff.gen(function* () { + const bytes = yield* ParseResult.encode(FromBytes)(toA) + let prefix: string + switch (toA._tag) { + case "BaseAddress": + case "EnterpriseAddress": + case "PointerAddress": + prefix = toA.networkId === 0 ? "addr_test" : "addr" + break + case "RewardAccount": + prefix = toA.networkId === 0 ? "stake_test" : "stake" + break + case "ByronAddress": + return yield* ParseResult.fail( + new ParseResult.Type(ast, toA, "Byron addresses do not support Bech32 encoding") + ) + } + const result = yield* Eff.try({ + try: () => { + const words = bech32.toWords(bytes) + return bech32.encode(prefix, words, false) + }, + catch: (error) => new ParseResult.Type(ast, toA, `Failed to encode Bech32: ${(error as Error).message}`) + }) + return result + }), + decode: (fromA, _, ast) => + Eff.gen(function* () { + const result = yield* Eff.try({ + try: () => { + const decoded = bech32.decode(fromA as any, false) + const bytes = bech32.fromWords(decoded.words) + return new Uint8Array(bytes) + }, + catch: (error) => new ParseResult.Type(ast, fromA, `Failed to decode Bech32: ${(error as Error).message}`) + }) + return yield* ParseResult.decode(FromBytes)(result) + }) +}).annotations({ + identifier: "Address.FromBech32", + description: "Transforms Bech32 string to Address" +}) + +/** + * Checks if two addresses are equal. + * + * @since 2.0.0 + * @category utils + */ +export const equals = (a: AddressEras, b: AddressEras): boolean => { + if (a._tag !== b._tag) { + return false + } + switch (a._tag) { + case "BaseAddress": + return BaseAddress.equals(a, b as BaseAddress.BaseAddress) + case "EnterpriseAddress": + return EnterpriseAddress.equals(a, b as EnterpriseAddress.EnterpriseAddress) + case "PointerAddress": + return PointerAddress.equals(a, b as PointerAddress.PointerAddress) + case "RewardAccount": + return RewardAccount.equals(a, b as RewardAccount.RewardAccount) + case "ByronAddress": + return false + } +} + +/** + * FastCheck arbitrary for Address instances. + * + * @since 2.0.0 + * @category arbitrary + * + */ +export const arbitrary = FastCheck.oneof( + BaseAddress.arbitrary, + EnterpriseAddress.arbitrary, + PointerAddress.arbitrary, + RewardAccount.arbitrary +) + +// ============================================================================ +// Parsing Functions +// ============================================================================ + +/** + * Parse an Address from bytes. + * + * @since 2.0.0 + * @category parsing + */ +export const fromBytes = Function.makeDecodeSync(FromBytes, AddressError, "Address.fromBytes") + +/** + * Parse an Address from hex string. + * + * @since 2.0.0 + * @category parsing + */ +export const fromHex = Function.makeDecodeSync(FromHex, AddressError, "Address.fromHex") + +/** + * Parse an Address from Bech32 string. + * + * @since 2.0.0 + * @category parsing + */ +export const fromBech32 = Function.makeDecodeSync(FromBech32, AddressError, "Address.fromBech32") + +// ============================================================================ +// Encoding Functions +// ============================================================================ + +/** + * Convert an Address to bytes. + * + * @since 2.0.0 + * @category encoding + */ +export const toBytes = Function.makeEncodeSync(FromBytes, AddressError, "Address.toBytes") + +/** + * Convert an Address to hex string. + * + * @since 2.0.0 + * @category encoding + */ +export const toHex = Function.makeEncodeSync(FromHex, AddressError, "Address.toHex") + +/** + * Convert an Address to Bech32 string. + * + * @since 2.0.0 + * @category encoding + */ +export const toBech32 = Function.makeEncodeSync(FromBech32, AddressError, "Address.toBech32") + +/** + * Effect-based error handling variants for functions that can fail. + * + * @since 2.0.0 + * @category effect + */ +export namespace Either { + export const fromBytes = Function.makeDecodeEither(FromBytes, AddressError) + export const fromHex = Function.makeDecodeEither(FromHex, AddressError) + export const fromBech32 = Function.makeDecodeEither(FromBech32, AddressError) + + export const toBytes = Function.makeEncodeEither(FromBytes, AddressError) + export const toHex = Function.makeEncodeEither(FromHex, AddressError) + export const toBech32 = Function.makeEncodeEither(FromBech32, AddressError) +} diff --git a/packages/evolution/src/AddressStructure.ts b/packages/evolution/src/AddressStructure.ts new file mode 100644 index 00000000..d72f0680 --- /dev/null +++ b/packages/evolution/src/AddressStructure.ts @@ -0,0 +1,282 @@ +/** + * @since 1.0.0 + */ + +import { bech32 } from "@scure/base" +import { Data, Effect as Eff, FastCheck, ParseResult, Schema } from "effect" + +import * as Address from "./Address.js" +import * as Bytes from "./Bytes.js" +import * as Bytes29 from "./Bytes29.js" +import * as Bytes57 from "./Bytes57.js" +import * as Credential from "./Credential.js" +import * as Function from "./Function.js" +import * as KeyHash from "./KeyHash.js" +import * as NetworkId from "./NetworkId.js" +import * as ScriptHash from "./ScriptHash.js" + +export class AddressStructureError extends Data.TaggedError("AddressStructureError")<{ + message?: string + cause?: unknown +}> {} + +/** + * @since 1.0.0 + * @category Schema + */ +export class AddressStructure extends Schema.Class("AddressStructure")({ + networkId: NetworkId.NetworkId, + paymentCredential: Credential.Credential, + stakingCredential: Schema.optional(Credential.Credential) +}) { + toString(): string { + const staking = this.stakingCredential ? `, stakingCredential: ${this.stakingCredential}` : "" + return `AddressStructure(${this.networkId === 0 ? "testnet" : "mainnet"}:${this.paymentCredential}${staking})` + } + + [Symbol.for("nodejs.util.inspect.custom")](): string { + return this.toString() + } +} + +/** + * Transform from bytes to AddressStructure + * Handles both BaseAddress (57 bytes) and EnterpriseAddress (29 bytes) + * + * @since 1.0.0 + * @category Transformations + */ +export const FromBytes = Schema.transformOrFail( + Schema.Union(Bytes57.BytesSchema, Bytes29.BytesSchema), + AddressStructure, + { + strict: true, + encode: (_, __, ___, toA) => + Eff.gen(function* () { + if (toA.stakingCredential) { + // BaseAddress encoding (57 bytes) + const paymentBit = toA.paymentCredential._tag === "KeyHash" ? 0 : 1 + const stakeBit = toA.stakingCredential._tag === "KeyHash" ? 0 : 1 + const header = (0b00 << 6) | (stakeBit << 5) | (paymentBit << 4) | (toA.networkId & 0b00001111) + const result = new Uint8Array(57) + result[0] = header + const paymentCredentialBytes = toA.paymentCredential.hash + result.set(paymentCredentialBytes, 1) + const stakeCredentialBytes = toA.stakingCredential.hash + result.set(stakeCredentialBytes, 29) + return yield* ParseResult.succeed(result) + } else { + // EnterpriseAddress encoding (29 bytes) + const paymentBit = toA.paymentCredential._tag === "KeyHash" ? 0 : 1 + const header = (0b01 << 6) | (0b1 << 5) | (paymentBit << 4) | (toA.networkId & 0b00001111) + const result = new Uint8Array(29) + result[0] = header + const paymentCredentialBytes = toA.paymentCredential.hash + result.set(paymentCredentialBytes, 1) + return yield* ParseResult.succeed(result) + } + }), + decode: (fromI, options, ast, fromA) => + Eff.gen(function* () { + const header = fromA[0] + const networkId = header & 0b00001111 + const addressTypeBits = (header >> 4) & 0b1111 + + if (fromA.length === 57) { + // BaseAddress (with staking credential) + const isPaymentKey = (addressTypeBits & 0b0001) === 0 + const paymentCredential: Credential.Credential = isPaymentKey + ? new KeyHash.KeyHash({ hash: fromA.slice(1, 29) }) + : new ScriptHash.ScriptHash({ hash: fromA.slice(1, 29) }) + + const isStakeKey = (addressTypeBits & 0b0010) === 0 + const stakingCredential: Credential.Credential = isStakeKey + ? new KeyHash.KeyHash({ hash: fromA.slice(29, 57) }) + : new ScriptHash.ScriptHash({ hash: fromA.slice(29, 57) }) + + return yield* ParseResult.decode(AddressStructure)({ + networkId, + paymentCredential, + stakingCredential + }) + } else if (fromA.length === 29) { + // EnterpriseAddress (no staking credential) + const isPaymentKey = (addressTypeBits & 0b0001) === 0 + const paymentCredential: Credential.Credential = isPaymentKey + ? new KeyHash.KeyHash({ hash: fromA.slice(1, 29) }) + : new ScriptHash.ScriptHash({ hash: fromA.slice(1, 29) }) + + return yield* ParseResult.decode(AddressStructure)({ + networkId, + paymentCredential, + stakingCredential: undefined + }) + } else { + return yield* ParseResult.fail(new ParseResult.Type(ast, fromA, "Invalid address length")) + } + }) + } +).annotations({ + identifier: "AddressStructure.FromBytes" +}) + +/** + * Transform from hex string to AddressStructure + * + * @since 1.0.0 + * @category Transformations + */ +export const FromHex = Schema.compose(Bytes.FromHex, FromBytes).annotations({ + identifier: "AddressStructure.FromHex" +}) + +/** + * Transform from Bech32 string to AddressStructure + * + * @since 1.0.0 + * @category Transformations + */ +export const FromBech32 = Schema.transformOrFail(Schema.String, AddressStructure, { + strict: true, + encode: (_, __, ___, toA) => + Eff.gen(function* () { + const prefix = toA.networkId === 0 ? "addr_test" : "addr" + const bytes = yield* ParseResult.encode(FromBytes)(toA) + const words = bech32.toWords(bytes) + return bech32.encode(prefix, words, false) + }), + decode: (fromA, _, ast) => + Eff.gen(function* () { + const result = yield* Eff.try({ + try: () => { + const decoded = bech32.decode(fromA as any, false) + const bytes = bech32.fromWords(decoded.words) + return new Uint8Array(bytes) + }, + catch: () => new ParseResult.Type(ast, fromA, `Failed to decode Bech32: ${fromA}`) + }) + return yield* ParseResult.decode(FromBytes)(result) + }) +}).annotations({ + identifier: "AddressStructure.FromBech32", + description: "Transforms Bech32 string to AddressStructure" +}) + +/** + * Check if two AddressStructure instances are equal. + * + * @since 1.0.0 + * @category Utils + */ +export const equals = (a: AddressStructure, b: AddressStructure): boolean => + a.networkId === b.networkId && + Credential.equals(a.paymentCredential, b.paymentCredential) && + ((a.stakingCredential === undefined && b.stakingCredential === undefined) || + (a.stakingCredential !== undefined && + b.stakingCredential !== undefined && + Credential.equals(a.stakingCredential, b.stakingCredential))) + +/** + * Check if AddressStructure has staking credential (BaseAddress-like) + * + * @since 1.0.0 + * @category Utils + */ +export const hasStakingCredential = (address: AddressStructure): boolean => address.stakingCredential !== undefined + +/** + * Check if AddressStructure is enterprise-like (no staking credential) + * + * @since 1.0.0 + * @category Utils + */ +export const isEnterprise = (address: AddressStructure): boolean => address.stakingCredential === undefined + +/** + * Get network ID from AddressStructure + * + * @since 1.0.0 + * @category Utils + */ +export const getNetworkId = (address: AddressStructure): NetworkId.NetworkId => address.networkId + +/** + * Sync functions using Function module utilities + * + * @since 1.0.0 + * @category Functions + */ +export const fromBech32 = Function.makeDecodeSync(FromBech32, AddressStructureError, "fromBech32") +export const toBech32 = Function.makeEncodeSync(FromBech32, AddressStructureError, "toBech32") +export const fromHex = Function.makeDecodeSync(FromHex, AddressStructureError, "fromHex") +export const toHex = Function.makeEncodeSync(FromHex, AddressStructureError, "toHex") +export const fromBytes = Function.makeDecodeSync(FromBytes, AddressStructureError, "fromBytes") +export const toBytes = Function.makeEncodeSync(FromBytes, AddressStructureError, "toBytes") + +/** + * FastCheck arbitrary generator for testing + * + * @since 1.0.0 + * @category Arbitrary + */ +export const arbitrary = FastCheck.record({ + networkId: NetworkId.arbitrary, + paymentCredential: Credential.arbitrary, + stakingCredential: FastCheck.option(Credential.arbitrary) +}).map( + (props) => + new AddressStructure({ + ...props, + stakingCredential: props.stakingCredential ?? undefined + }) +) + +export namespace Either { + /** + * Parse an AddressStructure from bytes. + * + * @since 1.0.0 + * @category parsing + */ + export const fromBytes = Function.makeDecodeEither(FromBytes, AddressStructureError) + + /** + * Parse an AddressStructure from hex string. + * + * @since 1.0.0 + * @category parsing + */ + export const fromHex = Function.makeDecodeEither(FromHex, AddressStructureError) + + /** + * Convert an AddressStructure to bytes. + * + * @since 1.0.0 + * @category encoding + */ + export const toBytes = Function.makeEncodeEither(FromBytes, AddressStructureError) + + /** + * Convert an AddressStructure to hex string. + * + * @since 1.0.0 + * @category encoding + */ + export const toHex = Function.makeEncodeEither(FromHex, AddressStructureError) + + /** + * Convert AddressStructure to Bech32 string. + * + * @since 1.0.0 + * @category encoding + */ + export const toBech32 = Function.makeEncodeEither(FromBech32, AddressStructureError) + + /** + * Parse an AddressStructure from Bech32 string. + * + * @since 1.0.0 + * @category parsing + */ + export const fromBech32 = Function.makeDecodeEither(FromBech32, AddressStructureError) +} diff --git a/packages/evolution/src/Bytes.ts b/packages/evolution/src/Bytes.ts index efb63b3a..04622488 100644 --- a/packages/evolution/src/Bytes.ts +++ b/packages/evolution/src/Bytes.ts @@ -189,14 +189,12 @@ export const BytesFromHexLenient = makeBytesTransformation({ * @category composition */ export const hexLengthEquals = - (byteLength: number, moduleName: string) => + (byteLength: number) => >(baseSchema: S) => baseSchema.pipe( Schema.filter((hex: string) => hex.length === byteLength * 2, { message: (issue) => - `${issue.actual} Must be exactly ${byteLength * 2} hex characters (${byteLength} bytes), got ${(issue.actual as string).length}`, - - identifier: `${moduleName}.LengthEquals${byteLength}` + `${issue.actual} Must be exactly ${byteLength * 2} hex characters (${byteLength} bytes), got ${(issue.actual as string).length}` }) ) @@ -232,12 +230,11 @@ export const hexLengthBetween = * @category composition */ export const hexLengthMin = - (minBytes: number, moduleName: string) => + (minBytes: number) => >(baseSchema: S) => baseSchema.pipe( Schema.filter((hex: string) => hex.length >= minBytes * 2, { - message: () => `Must be at least ${minBytes} bytes (${minBytes * 2} hex characters)`, - identifier: `${moduleName}.LengthMin${minBytes}` + message: () => `Must be at least ${minBytes} bytes (${minBytes * 2} hex characters)` }) ) @@ -249,12 +246,11 @@ export const hexLengthMin = * @category composition */ export const hexLengthMax = - (maxBytes: number, moduleName: string) => + (maxBytes: number) => >(baseSchema: S) => baseSchema.pipe( Schema.filter((hex: string) => hex.length <= maxBytes * 2, { - message: () => `Must be at most ${maxBytes} bytes (${maxBytes * 2} hex characters)`, - identifier: `${moduleName}.LengthMax${maxBytes}` + message: () => `Must be at most ${maxBytes} bytes (${maxBytes * 2} hex characters)` }) ) @@ -266,12 +262,11 @@ export const hexLengthMax = * @category composition */ export const hexStartsWithPrefix = - (prefix: string, moduleName: string) => + (prefix: string) => >(baseSchema: S) => baseSchema.pipe( Schema.filter((hex: string) => hex.toLowerCase().startsWith(prefix.toLowerCase()), { - message: () => `Must start with prefix "${prefix}"`, - identifier: `${moduleName}.StartsWithPrefix` + message: () => `Must start with prefix "${prefix}"` }) ) @@ -284,13 +279,12 @@ export const hexStartsWithPrefix = * @category composition */ export const bytesLengthEquals = - (byteLength: number, moduleName: string) => + (byteLength: number) => >(baseSchema: S) => baseSchema.pipe( Schema.filter((bytes: Uint8Array) => bytes.length === byteLength, { message: (issue) => - `${issue.actual} Must be exactly ${byteLength} bytes, got ${(issue.actual as Uint8Array).length}`, - identifier: `${moduleName}.BytesLengthEquals${byteLength}` + `${issue.actual} Must be exactly ${byteLength} bytes, got ${(issue.actual as Uint8Array).length}` }) ) @@ -302,12 +296,11 @@ export const bytesLengthEquals = * @category composition */ export const bytesLengthBetween = - (minBytes: number, maxBytes: number, moduleName: string) => + (minBytes: number, maxBytes: number) => >(baseSchema: S) => baseSchema.pipe( Schema.filter((bytes: Uint8Array) => bytes.length >= minBytes && bytes.length <= maxBytes, { - message: () => `Must be between ${minBytes} and ${maxBytes} bytes`, - identifier: `${moduleName}.BytesLengthBetween${minBytes}And${maxBytes}` + message: () => `Must be between ${minBytes} and ${maxBytes} bytes` }) ) @@ -319,12 +312,11 @@ export const bytesLengthBetween = * @category composition */ export const bytesLengthMin = - (minBytes: number, moduleName: string) => + (minBytes: number) => >(baseSchema: S) => baseSchema.pipe( Schema.filter((bytes: Uint8Array) => bytes.length >= minBytes, { - message: () => `Must be at least ${minBytes} bytes`, - identifier: `${moduleName}.BytesLengthMin${minBytes}` + message: () => `Must be at least ${minBytes} bytes` }) ) @@ -353,7 +345,7 @@ export const bytesLengthMax = * @category composition */ export const bytesStartsWithPrefix = - (prefix: Uint8Array, moduleName: string) => + (prefix: Uint8Array) => >(baseSchema: S) => baseSchema.pipe( Schema.filter( @@ -365,8 +357,7 @@ export const bytesStartsWithPrefix = return true }, { - message: () => `Must start with prefix [${Array.from(prefix).join(", ")}]`, - identifier: `${moduleName}.BytesStartsWithPrefix` + message: () => `Must start with prefix [${Array.from(prefix).join(", ")}]` } ) ) diff --git a/packages/evolution/src/NetworkId.ts b/packages/evolution/src/NetworkId.ts index fbf27881..05fa1560 100644 --- a/packages/evolution/src/NetworkId.ts +++ b/packages/evolution/src/NetworkId.ts @@ -18,7 +18,7 @@ export class NetworkIdError extends Data.TaggedError("NetworkIdError")<{ * @since 2.0.0 * @category schemas */ -export const NetworkId = Schema.NonNegativeInt.pipe(Schema.brand("NetworkId")).annotations({ +export const NetworkId = Schema.NonNegativeInt.annotations({ identifier: "NetworkId" }) diff --git a/packages/evolution/src/Script.ts b/packages/evolution/src/Script.ts index 2205bd92..2c95cd28 100644 --- a/packages/evolution/src/Script.ts +++ b/packages/evolution/src/Script.ts @@ -1,6 +1,7 @@ import { Data, Effect as Eff, FastCheck, ParseResult, Schema } from "effect" import * as CBOR from "./CBOR.js" +import * as Function from "./Function.js" import * as NativeScripts from "./NativeScripts.js" import * as PlutusV1 from "./PlutusV1.js" import * as PlutusV2 from "./PlutusV2.js" @@ -171,3 +172,8 @@ export const arbitrary: FastCheck.Arbitrary