From 027a158f8175b132fb1cc5d11c08c0199c4421fd Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Mon, 28 Sep 2026 14:58:41 +0100 Subject: [PATCH] Adapt dynamic-codecs to Codama v2 --- .../dynamic-address-resolution/package.json | 6 +- packages/dynamic-client/package.json | 8 +- packages/dynamic-codecs/README.md | 197 ++-- packages/dynamic-codecs/package.json | 5 +- packages/dynamic-codecs/src/codecs.ts | 452 ++++----- packages/dynamic-codecs/src/index.ts | 21 +- packages/dynamic-codecs/src/values.ts | 98 +- .../test/codecs/AccountLinkNode.test.ts | 22 + .../test/codecs/AccountNode.test.ts | 17 +- .../test/codecs/ArrayTypeNode.test.ts | 16 +- .../test/codecs/BooleanTypeNode.test.ts | 14 +- .../test/codecs/BytesTypeNode.test.ts | 8 +- .../test/codecs/DateTimeTypeNode.test.ts | 11 + .../test/codecs/DefinedTypeLinkNode.test.ts | 106 ++- .../test/codecs/DefinedTypeNode.test.ts | 89 +- .../test/codecs/DurationTypeNode.test.ts | 11 + .../test/codecs/EnumTypeNode.test.ts | 180 ++-- .../test/codecs/EventNode.test.ts | 17 +- .../test/codecs/FixedPointTypeNode.test.ts | 12 + .../test/codecs/FloatTypeNode.test.ts | 23 + .../test/codecs/HiddenPrefixTypeNode.test.ts | 22 - .../test/codecs/HiddenSuffixTypeNode.test.ts | 22 - .../codecs/InstructionArgumentNode.test.ts | 16 - .../test/codecs/InstructionLinkNode.test.ts | 22 + .../test/codecs/InstructionNode.test.ts | 63 +- .../test/codecs/IntegerTypeNode.test.ts | 84 ++ .../test/codecs/MapTypeNode.test.ts | 37 +- .../test/codecs/NumberTypeNode.test.ts | 85 -- .../test/codecs/OptionTypeNode.test.ts | 16 +- .../test/codecs/PostOffsetTypeNode.test.ts | 42 - .../test/codecs/PreOffsetTypeNode.test.ts | 32 - .../test/codecs/PublicKeyTypeNode.test.ts | 4 +- .../codecs/RemainderOptionTypeNode.test.ts | 8 +- .../test/codecs/SentinelCountNode.test.ts | 83 ++ .../test/codecs/SentinelTypeNode.test.ts | 12 - .../test/codecs/SetTypeNode.test.ts | 16 +- .../test/codecs/SizePrefixTypeNode.test.ts | 11 - .../test/codecs/StringTypeNode.test.ts | 10 +- .../test/codecs/StructFieldTypeNode.test.ts | 8 +- .../test/codecs/StructTypeNode.test.ts | 32 +- .../test/codecs/TupleTypeNode.test.ts | 10 +- .../codecs/ZeroableOptionTypeNode.test.ts | 12 +- .../FixedSizeTransformNode.test.ts} | 8 +- .../HiddenPrefixTransformNode.test.ts | 19 + .../HiddenSuffixTransformNode.test.ts | 19 + .../PostOffsetTransformNode.test.ts | 58 ++ .../transforms/PreOffsetTransformNode.test.ts | 35 + .../transforms/SentinelTransformNode.test.ts | 12 + .../SizePrefixTransformNode.test.ts | 19 + .../codecs/transforms/TransformOrder.test.ts | 38 + .../test/values/ArrayValueNode.test.ts | 6 +- .../test/values/BooleanValueNode.test.ts | 11 + .../test/values/ConstantValueNode.test.ts | 31 +- .../test/values/EnumValueNode.test.ts | 131 +-- .../test/values/FloatValueNode.test.ts | 10 + .../test/values/InjectedValueNode.test.ts | 50 +- .../test/values/IntegerValueNode.test.ts | 15 + .../test/values/MapValueNode.test.ts | 10 +- .../test/values/NumberValueNode.test.ts | 10 - .../test/values/SetValueNode.test.ts | 6 +- .../test/values/StructValueNode.test.ts | 12 +- .../test/values/TupleValueNode.test.ts | 6 +- packages/dynamic-instructions/package.json | 10 +- packages/dynamic-parsers/package.json | 4 +- packages/errors/src/codes.ts | 2 + packages/errors/src/context.ts | 6 + packages/errors/src/messages.ts | 3 + packages/nodes-from-anchor/package.json | 2 +- packages/visitors-core/src/ProvidedScope.ts | 86 +- .../visitors-core/src/getByteSizeVisitor.ts | 8 +- .../src/getInstructionDataFields.ts | 7 +- .../src/getMaxByteSizeVisitor.ts | 8 +- .../src/recordNodeStackVisitor.ts | 8 +- .../visitors-core/test/ProvidedScope.test.ts | 56 ++ .../test/recordNodeStackVisitor.test.ts | 23 + .../visitors/src/unwrapDefinedTypesVisitor.ts | 10 +- pnpm-lock.yaml | 863 +++++++++--------- 77 files changed, 2134 insertions(+), 1428 deletions(-) create mode 100644 packages/dynamic-codecs/test/codecs/AccountLinkNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/DateTimeTypeNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/DurationTypeNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/FixedPointTypeNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/FloatTypeNode.test.ts delete mode 100644 packages/dynamic-codecs/test/codecs/HiddenPrefixTypeNode.test.ts delete mode 100644 packages/dynamic-codecs/test/codecs/HiddenSuffixTypeNode.test.ts delete mode 100644 packages/dynamic-codecs/test/codecs/InstructionArgumentNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/InstructionLinkNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/IntegerTypeNode.test.ts delete mode 100644 packages/dynamic-codecs/test/codecs/NumberTypeNode.test.ts delete mode 100644 packages/dynamic-codecs/test/codecs/PostOffsetTypeNode.test.ts delete mode 100644 packages/dynamic-codecs/test/codecs/PreOffsetTypeNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/SentinelCountNode.test.ts delete mode 100644 packages/dynamic-codecs/test/codecs/SentinelTypeNode.test.ts delete mode 100644 packages/dynamic-codecs/test/codecs/SizePrefixTypeNode.test.ts rename packages/dynamic-codecs/test/codecs/{FixedSizeTypeNode.test.ts => transforms/FixedSizeTransformNode.test.ts} (56%) create mode 100644 packages/dynamic-codecs/test/codecs/transforms/HiddenPrefixTransformNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/transforms/HiddenSuffixTransformNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/transforms/PostOffsetTransformNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/transforms/PreOffsetTransformNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/transforms/SentinelTransformNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/transforms/SizePrefixTransformNode.test.ts create mode 100644 packages/dynamic-codecs/test/codecs/transforms/TransformOrder.test.ts create mode 100644 packages/dynamic-codecs/test/values/BooleanValueNode.test.ts create mode 100644 packages/dynamic-codecs/test/values/FloatValueNode.test.ts create mode 100644 packages/dynamic-codecs/test/values/IntegerValueNode.test.ts delete mode 100644 packages/dynamic-codecs/test/values/NumberValueNode.test.ts diff --git a/packages/dynamic-address-resolution/package.json b/packages/dynamic-address-resolution/package.json index 766916845..fdff0792c 100644 --- a/packages/dynamic-address-resolution/package.json +++ b/packages/dynamic-address-resolution/package.json @@ -75,13 +75,13 @@ "dependencies": { "@codama/dynamic-codecs": "workspace:*", "@codama/errors": "workspace:*", - "@solana/addresses": "^8.3.0", - "@solana/codecs": "^8.3.0", + "@solana/addresses": "^8.4.0", + "@solana/codecs": "^8.4.0", "codama": "workspace:*", "commander": "^15.0.0" }, "devDependencies": { - "@solana/kit": "8.3.0" + "@solana/kit": "8.4.0" }, "browserslist": [ "supports bigint and not dead", diff --git a/packages/dynamic-client/package.json b/packages/dynamic-client/package.json index 81d441e6d..1a33eb8ff 100644 --- a/packages/dynamic-client/package.json +++ b/packages/dynamic-client/package.json @@ -66,9 +66,9 @@ "@codama/dynamic-codecs": "workspace:*", "@codama/dynamic-instructions": "workspace:*", "@codama/errors": "workspace:*", - "@solana/addresses": "^8.3.0", - "@solana/codecs": "^8.3.0", - "@solana/instructions": "^8.3.0", + "@solana/addresses": "^8.4.0", + "@solana/codecs": "^8.4.0", + "@solana/instructions": "^8.4.0", "codama": "workspace:*", "commander": "^15.0.0", "superstruct": "^2.0.2" @@ -80,7 +80,7 @@ "@solana-program/program-metadata": "^0.9.3", "@solana-program/token": "0.16.1", "@solana-program/token-2022": "^0.17.0", - "@solana/kit": "8.3.0", + "@solana/kit": "8.4.0", "litesvm": "1.4.1", "sas-lib": "2.0.0-beta.1" }, diff --git a/packages/dynamic-codecs/README.md b/packages/dynamic-codecs/README.md index e49ea2e3b..9dbdc79cb 100644 --- a/packages/dynamic-codecs/README.md +++ b/packages/dynamic-codecs/README.md @@ -7,7 +7,7 @@ [npm-image]: https://img.shields.io/npm/v/@codama/dynamic-codecs.svg?style=flat&label=%40codama%2Fdynamic-codecs [npm-url]: https://www.npmjs.com/package/@codama/dynamic-codecs -This package provides a set of helpers that provide `Codecs` for Codama nodes that describe data. +This package creates [`@solana/codecs`](https://github.com/anza-xyz/kit/tree/main/packages/codecs) on demand for any Codama node that describes data, so you can encode and decode accounts, instructions, events and types straight from a Codama IDL. ## Installation @@ -18,100 +18,153 @@ pnpm install @codama/dynamic-codecs > [!NOTE] > This package is **not** included in the main [`codama`](../library) package. -## Functions +## Usage -### `getNodeCodec(path, options?)` - -Given the full `NodePath` of a node inside a Codama IDL, returns a `Codec` (as defined in `@solana/codecs`) that enables encoding and decoding data for that node. +Give `getNodeValueCodec` the path to a node, starting from the root node, and it returns a `Codec` for that node. ```ts -const codec = getNodeCodec([root, program, definedType]); -const bytes = codec.encode(someData); -const decodedData = codec.decode(bytes); +import { getNodeValueCodec } from '@codama/dynamic-codecs'; + +const codec = getNodeValueCodec([root, program, counterAccount]); +const bytes = codec.encode({ authority: '9BbWp6tcX9MEGSUEpNXfspYxYsWCxE9FgRkAc3RpftkT', count: 42 }); +const counter = codec.decode(bytes); +// ^ { authority: '9BbWp6tcX9MEGSUEpNXfspYxYsWCxE9FgRkAc3RpftkT', count: 42n } ``` -Note that it is important to provide the full `NodePath` of the node in order to properly follow link nodes inside the Codama IDL. Here is a more complex example illustrating how link nodes are resolved: +## Node paths + +The full path is needed to resolve link nodes, which may point to other programs, and injected values, which are provided by the enclosing instructions. ```ts -// Here we define a program with two types, one of which is a link to the other. const root = rootNode( programNode({ definedTypes: [ - definedTypeNode({ name: 'slot', type: numberTypeNode('u64') }), - definedTypeNode({ name: 'lastSlot', type: definedTypeLinkNode('slot') }), + definedTypeNode({ identifier: 'slot', type: integerTypeNode('u64') }), + definedTypeNode({ identifier: 'lastSlot', type: definedTypeLinkNode('slot') }), ], - name: 'myProgram', + identifier: 'myProgram', publicKey: '1111', }), ); -// The codec for the linked `lastSlot` defined type is resolved using the `slot` defined type. -const codec = getNodeCodec([root, root.program, root.program.definedTypes[1]]); -expect(codec.encode(42)).toStrictEqual(hex('2a00000000000000')); -expect(codec.decode(hex('2a00000000000000'))).toBe(42n); +// The `lastSlot` codec is resolved using the linked `slot` type. +const codec = getNodeValueCodec([root, root.program, root.program.definedTypes[1]]); +codec.encode(42); // 0x2a00000000000000 +codec.decode(hex('2a00000000000000')); // 42n +``` + +The following nodes can be at the end of the path: `AccountNode`, `AccountLinkNode`, `DefinedTypeNode`, `DefinedTypeLinkNode`, `EventNode`, `InstructionNode`, `InstructionLinkNode`, `StructFieldTypeNode`, `EnumVariantTypeNode` and any type node. + +## Options + +| Name | Type | Default | Description | +| --------------- | --------------- | ---------- | -------------------------------------------- | +| `bytesEncoding` | `BytesEncoding` | `"base64"` | The encoding used when decoding plain bytes. | + +```ts +const codec = getNodeValueCodec([root, program, definedType], { bytesEncoding: 'base16' }); +``` + +## Value format + +Values are raw JavaScript values that stay close to the bytes. For instance, a fixed point decodes to its raw integer, not to a decimal number. Types such as `Uint8Array`, `Set` or `Map` are avoided to keep values JSON compatible, with the exception of `bigint`. + +| Node | Example | Notes | +| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------- | +| [`IntegerTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/IntegerTypeNode.md) | `42n` | Always decodes to a `bigint`, whatever its size. Encodes from a `number` or a `bigint`. | +| [`FloatTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/FloatTypeNode.md) | `1.5` | | +| [`FixedPointTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/FixedPointTypeNode.md) | `150n` | The raw integer, e.g. `1.5` with a scale of `2`. | +| [`DateTimeTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/DateTimeTypeNode.md) | `1700000000n` | The raw integer, in ticks. | +| [`DurationTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/DurationTypeNode.md) | `1500n` | The raw integer, in ticks. | +| [`BooleanTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/BooleanTypeNode.md) | `true` | | +| [`StringTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/StringTypeNode.md) | `"Hello World"` | Uses the encoding of the node. | +| [`BytesTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/BytesTypeNode.md) | `["base64", "SGVsbG8="]` | Encodes using the given encoding and decodes using the `bytesEncoding` option. | +| [`PublicKeyTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/PublicKeyTypeNode.md) | `"9BbWp6tcX9MEGSUEpNXfspYxYsWCxE9FgRkAc3RpftkT"` | A base58 string. | +| [`StructTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/StructTypeNode.md) | `{ first_name: "John", age: 42n }` | Keys are the raw field identifiers. | +| [`TupleTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/TupleTypeNode.md) | `["John", 42n]` | | +| [`EnumTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/EnumTypeNode.md) | `{ __kind: "move", __discriminator: 2, data: { x: 1n } }` | See [Enums](#enums). | +| [`ArrayTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/ArrayTypeNode.md) | `[1n, 2n, 3n]` | | +| [`SetTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/SetTypeNode.md) | `[1n, 2n, 3n]` | Same as arrays. | +| [`MapTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/MapTypeNode.md) | `{ key1: "value1", key2: "value2" }` | An object. | +| [`OptionTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/OptionTypeNode.md) | `{ __option: "Some", value: 42n }` or `{ __option: "None" }` | Option objects, rather than `T \| null`, keep nested options distinct. | +| [`RemainderOptionTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/RemainderOptionTypeNode.md) | `{ __option: "Some", value: 42n }` or `{ __option: "None" }` | Same as options. | +| [`ZeroableOptionTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/ZeroableOptionTypeNode.md) | `{ __option: "Some", value: 42n }` or `{ __option: "None" }` | Same as options. | +| [`StructFieldTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/StructFieldTypeNode.md) | - | Same as `node.type`. | +| [`EnumVariantTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/typeNodes/EnumVariantTypeNode.md) | - | Same as `node.data`, without the enum discriminator. | +| [`DefinedTypeNode`](https://github.com/codama-idl/spec/blob/main/docs/DefinedTypeNode.md) | - | Same as `node.type`. | +| [`AccountNode`](https://github.com/codama-idl/spec/blob/main/docs/AccountNode.md) | - | Same as `node.data`. | +| [`EventNode`](https://github.com/codama-idl/spec/blob/main/docs/EventNode.md) | - | Same as `node.data`. | +| [`InstructionNode`](https://github.com/codama-idl/spec/blob/main/docs/InstructionNode.md) | - | Same as `node.data`. Instructions without data encode to empty bytes. | +| [`DefinedTypeLinkNode`](https://github.com/codama-idl/spec/blob/main/docs/linkNodes/DefinedTypeLinkNode.md) | - | Same as the linked type, with the transforms of the link on top. | +| [`AccountLinkNode`](https://github.com/codama-idl/spec/blob/main/docs/linkNodes/AccountLinkNode.md) | - | Same as the linked account. | +| [`InstructionLinkNode`](https://github.com/codama-idl/spec/blob/main/docs/linkNodes/InstructionLinkNode.md) | - | Same as the linked instruction. | + +### Enums + +Every enum decodes to an object with the raw variant identifier as `__kind` and the variant discriminator as `__discriminator`. Variants with data carry it under `data`, whatever its type. When encoding, only `__kind` and `data` are read. + +```ts +const codec = getNodeValueCodec([ + enumTypeNode([ + enumVariantTypeNode('quit'), + enumVariantTypeNode('amount', { data: integerTypeNode('u32') }), + enumVariantTypeNode('move', { + data: structTypeNode([structFieldTypeNode({ identifier: 'x', type: integerTypeNode('u8') })]), + }), + ]), +]); + +codec.decode(hex('00')); // { __kind: 'quit', __discriminator: 0 } +codec.decode(hex('012a000000')); // { __kind: 'amount', __discriminator: 1, data: 42n } +codec.decode(hex('020a')); // { __kind: 'move', __discriminator: 2, data: { x: 10n } } +``` + +### Transforms + +The `transforms` of a type node are applied in order, from the innermost to the outermost. Here, the size prefix wraps the fixed-size string. + +```ts +const codec = getNodeValueCodec([ + stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(4), sizePrefixTransformNode(integerTypeNode('u8'))] }), +]); + +codec.encode('Hi'); // 0x0448690000 +``` + +### Collections + +Arrays, sets and maps support every count node. With a `sentinelCountNode`, items are read until the sentinel is found at an item boundary, and its `strategy` controls whether the sentinel is written and required. + +```ts +const sentinel = constantValueNodeFromBytes('base16', 'ffff'); +const codec = getNodeValueCodec([arrayTypeNode(integerTypeNode('u16'), sentinelCountNode(sentinel))]); + +codec.encode([42, 99]); // 0x2a006300ffff +codec.decode(hex('2a006300ffff')); // [42n, 99n] ``` -#### Options - -The `getNodeCodec` function accepts the following options. - -| Name | Type | Default | Description | -| --------------- | --------------- | ---------- | -------------------------------------------------------- | -| `bytesEncoding` | `BytesEncoding` | `"base64"` | The default encoding to use when formatting plain bytes. | - -#### Decoded format - -In the table below, we illustrate the format of each codec based on the node from which it was created. - -Note that we purposefully avoid types such as `Uint8Array`, `Set` or `Map` in order to keep the format JSON compatible. For instance, plain bytes are not provided as `Uint8Array` but as a tuple of type `[BytesEncoding, string]` — e.g. `["base64", "HelloWorld++"]` — where the default bytes encoding is `base64` which is configurable via the `bytesEncoding` option. - -| Node | Example | Notes | -| ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| [`AccountLinkNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/linkNodes/AccountLinkNode.md) | - | Same as `AccountNode` | -| [`AccountNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/AccountNode.md) | - | Same as `node.data` | -| [`DefinedTypeLinkNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/linkNodes/DefinedTypeLinkNode.md) | - | Same as `DefinedTypeNode` | -| [`DefinedTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/DefinedTypeNode.md) | - | Same as `node.type` | -| [`InstructionArgumentLinkNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/linkNodes/InstructionArgumentLinkNode.md) | - | Same as `InstructionArgumentNode` | -| [`InstructionArgumentNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/InstructionArgumentNode.md) | - | Same as `node.type` | -| [`InstructionLinkNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/linkNodes/InstructionLinkNode.md) | - | Same as `InstructionNode` | -| [`InstructionNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/InstructionNode.md) | - | Same as a `StructTypeNode` containing all `node.arguments` | -| [`AmountTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/AmountTypeNode.md) | `42` | Same as `NumberTypeNode` | -| [`ArrayTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/ArrayTypeNode.md) | `[1, 2, 3]` | | -| [`BooleanTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/BooleanTypeNode.md) | `true` or `false` | | -| [`BytesTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/BytesTypeNode.md) | `["base16", "00ffaa"]` | Uses `bytesEncoding` option to decode | -| [`DateTimeTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/DateTimeTypeNode.md) | `42` | Same as `NumberTypeNode` | -| [`EnumTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/EnumTypeNode.md) | `{ __kind: "Quit", __discriminator: 0 }` | Discriminated unions for all enums; the wire honors `variant.discriminator`. | -| [`FixedSizeTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/FixedSizeTypeNode.md) | - | Same as `node.type` | -| [`HiddenPrefixTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/HiddenPrefixTypeNode.md) | - | Same as `node.type` | -| [`HiddenSuffixTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/HiddenSuffixTypeNode.md) | - | Same as `node.type` | -| [`MapTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/MapTypeNode.md) | `{ key1: "value1", key2: "value2" }` | Represent `Maps` as `objects` | -| [`NumberTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/NumberTypeNode.md) | `42` | This could be a `bigint` | -| [`OptionTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/OptionTypeNode.md) | `{ __option: "Some", value: 42 }` or `{ __option: "None" }` | Uses value objects (instead of `T \| null`) to avoid loosing information on nested options. | -| [`PostOffsetTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/PostOffsetTypeNode.md) | - | Same as `node.type` | -| [`PreOffsetTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/PreOffsetTypeNode.md) | - | Same as `node.type` | -| [`PublicKeyTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/PublicKeyTypeNode.md) | `"3QC7Pnv2KfwwdC44gPcmQWuZXmRSbUpmWMJnhenMC8CU"` | Uses base58 representations of public keys | -| [`RemainderOptionTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/RemainderOptionTypeNode.md) | `{ __option: "Some", value: 42 }` or `{ __option: "None" }` | Same as `OptionTypeNode` | -| [`SentinelTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/SentinelTypeNode.md) | - | Same as `node.type` | -| [`SetTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/SetTypeNode.md) | `[1, 2, 3]` | Same as `ArrayTypeNode` | -| [`SizePrefixTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/SizePrefixTypeNode.md) | - | Same as `node.type` | -| [`SolAmountTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/SolAmountTypeNode.md) | `42` | Same as `NumberTypeNode` | -| [`StringTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/StringTypeNode.md) | `"Hello World"` | Uses the encoding defined in the node — i.e. `node.encoding` | -| [`StructTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/StructTypeNode.md) | `{ name: "John", age: 42 }` | | -| [`TupleTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/TupleTypeNode.md) | `["John", 42]` | Uses arrays to create tuples | -| [`ZeroableOptionTypeNode`](https://github.com/codama-idl/spec/blob/main/v1/docs/typeNodes/ZeroableOptionTypeNode.md) | `{ __option: "Some", value: 42 }` or `{ __option: "None" }` | Same as `OptionTypeNode` | - -### `getNodeCodecVisitor(linkables, options?)` - -This visitor is used by `getNodeCodec` under the hood. It returns a `Codec` for the visited node. +## Visitors + +### `getNodeValueCodecVisitor(linkables, options?)` + +The visitor used by `getNodeValueCodec` under the hood. It returns a `Codec` for the visited node. On top of the `bytesEncoding` option, it accepts the `stack` of the visited node's ancestors, used to resolve links, and the `scope` of the enclosing instructions' provided values, used to resolve injected values. ```ts -return visit(someTypeNode, getNodeCodecVisitor(linkables)); +const codec = visit(someTypeNode, getNodeValueCodecVisitor(linkables, { scope, stack })); ``` ### `getValueNodeVisitor(linkables, options?)` -This visitor is used by the `getValueNodeVisitor` under the hood. It returns an `unknown` value for the visited `ValueNode`. +Returns the value of the visited value node, in the same format as the codecs. For instance, an `integerValueNode` returns a `bigint`, a `constantValueNode` returns its encoded bytes, and an `injectedValueNode` returns the value provided for it. + +```ts +visit(integerValueNode('42'), getValueNodeVisitor(linkables)); // 42n +``` + +### `getCodecAndValueVisitors(linkables, options?)` + +Returns both visitors, sharing the same `stack` and `scope`. ```ts -return visit(someValueNode, getValueNodeVisitor(linkables)); +const { codecVisitor, valueVisitor } = getCodecAndValueVisitors(linkables, { stack }); ``` diff --git a/packages/dynamic-codecs/package.json b/packages/dynamic-codecs/package.json index 8d1b1076f..2f2e05e95 100644 --- a/packages/dynamic-codecs/package.json +++ b/packages/dynamic-codecs/package.json @@ -56,7 +56,10 @@ "@codama/fragments": "workspace:*", "@codama/nodes": "workspace:*", "@codama/visitors-core": "workspace:*", - "@solana/codecs": "^8.3.0" + "@solana/codecs": "^8.4.0" + }, + "devDependencies": { + "@solana/errors": "^8.4.0" }, "browserslist": [ "supports bigint and not dead", diff --git a/packages/dynamic-codecs/src/codecs.ts b/packages/dynamic-codecs/src/codecs.ts index f2f6c65df..5094cc435 100644 --- a/packages/dynamic-codecs/src/codecs.ts +++ b/packages/dynamic-codecs/src/codecs.ts @@ -3,35 +3,36 @@ import { CODAMA_ERROR__UNRECOGNIZED_NUMBER_FORMAT, CodamaError, } from '@codama/errors'; -import { pascalCase } from '@codama/fragments/casing'; import { AccountLinkNode, AccountNode, BytesEncoding, + ConstantValueNode, CountNode, DefinedTypeLinkNode, DefinedTypeNode, EventNode, - InstructionArgumentLinkNode, - InstructionArgumentNode, + FloatTypeNode, InstructionLinkNode, InstructionNode, + IntegerTypeNode, isNode, - NumberFormat, RegisteredTypeNode, - structFieldTypeNode, - structFieldTypeNodeFromInstructionArgumentNode, - structTypeNode, - structTypeNodeFromInstructionArgumentNodes, + TransformNode, + TYPE_NODE_KINDS, + ValueNode, } from '@codama/nodes'; import { getLastNodeFromPath, getRecordLinkablesVisitor, + interceptVisitor, LinkableDictionary, NodePath, NodeStack, pipe, + ProvidedScope, recordNodeStackVisitor, + recordProvidedScopeVisitor, visit, Visitor, } from '@codama/visitors-core'; @@ -41,14 +42,15 @@ import { assertIsFixedSize, Codec, createCodec, + Endian, fixCodecSize, + FixedSizeNumberCodec, getArrayCodec, getBase16Codec, getBase58Codec, getBase64Codec, getBooleanCodec, getConstantCodec, - getUnionCodec, getF32Codec, getF64Codec, getHiddenPrefixCodec, @@ -58,7 +60,6 @@ import { getI32Codec, getI64Codec, getI128Codec, - getMapCodec, getOptionCodec, getShortU16Codec, getStructCodec, @@ -68,83 +69,183 @@ import { getU32Codec, getU64Codec, getU128Codec, + getUnionCodec, getUnitCodec, getUtf8Codec, NumberCodec, offsetCodec, padLeftCodec, padRightCodec, + ReadonlyUint8Array, transformCodec, } from '@solana/codecs'; import { getValueNodeVisitor } from './values'; +/** The node kinds a codec can be created for. */ export type EncodableNodes = | AccountLinkNode | AccountNode | DefinedTypeLinkNode | DefinedTypeNode | EventNode - | InstructionArgumentLinkNode - | InstructionArgumentNode | InstructionLinkNode | InstructionNode | RegisteredTypeNode; +/** Options shared by {@link getNodeValueCodec} and {@link getNodeValueCodecVisitor}. */ export type CodecVisitorOptions = { + /** The encoding used to decode plain bytes, e.g. `["base64", "SGVsbG8="]`. Defaults to `base64`. */ bytesEncoding?: BytesEncoding; }; -export function getNodeCodec(path: NodePath, options: CodecVisitorOptions = {}): Codec { +/** The decoded value of an enum variant, e.g. `{ __kind: 'move', __discriminator: 2, data: { x: 1n } }`. */ +type EnumVariantValue = { __discriminator?: number; __kind: string; data?: unknown }; + +/** + * Get a codec for the node at the end of the given path. Values are raw JavaScript + * values, e.g. integers are `bigint`s and enums are `{ __kind, __discriminator, data }`. + * + * The full path, from the root node, is needed to resolve link nodes and injected + * values, e.g. `[root, program, definedType]`. + * + * @example + * ```ts + * const codec = getNodeValueCodec([root, program, definedType]); + * const bytes = codec.encode({ amount: 42n }); + * const value = codec.decode(bytes); + * ``` + */ +export function getNodeValueCodec(path: NodePath, options: CodecVisitorOptions = {}): Codec { const linkables = new LinkableDictionary(); visit(path[0], getRecordLinkablesVisitor(linkables)); + // Open a frame for every enclosing instruction, so the ones `provides` resolve injected values. + const ancestors = path.slice(0, -1); + const frames = ancestors.flatMap(node => (isNode(node, 'instructionNode') ? [node.provides ?? []] : [])); + return visit( getLastNodeFromPath(path), - getNodeCodecVisitor(linkables, { - stack: new NodeStack(path.slice(0, -1)), + getNodeValueCodecVisitor(linkables, { ...options, + scope: new ProvidedScope(...frames), + stack: new NodeStack(ancestors), }), ); } -export function getNodeCodecVisitor( +/** + * A visitor that returns a codec for the visited node, as described by {@link getNodeValueCodec}. + * + * The `stack` must hold the ancestors of the visited node to resolve link nodes, and the + * `scope` must hold the `provides` of its enclosing instructions to resolve injected values. + */ +export function getNodeValueCodecVisitor( linkables: LinkableDictionary, - options: CodecVisitorOptions & { stack?: NodeStack } = {}, + options: CodecVisitorOptions & { scope?: ProvidedScope; stack?: NodeStack } = {}, ): Visitor, EncodableNodes['kind']> { const stack = options.stack ?? new NodeStack(); + const scope = options.scope ?? new ProvidedScope(); const bytesEncoding = options.bytesEncoding ?? 'base64'; - const valueNodeVisitor = getValueNodeVisitor(linkables, { - codecVisitorFactory: () => visitor, - stack, - }); + const valueVisitor = getValueNodeVisitor(linkables, { codecVisitorFactory: () => visitor, scope, stack }); + const getConstantBytes = (node: ConstantValueNode) => getConstantValueBytes(node, visitor, valueVisitor); + + const visitLinkedNode = ( + node: TLinkNode, + ) => { + const path = linkables.getPathOrThrow(stack.getPath(node.kind) as NodePath); + stack.pushPath(path); + try { + return visit(getLastNodeFromPath(path), visitor); + } finally { + stack.popPath(); + } + }; + + const getCollectionCodec = (item: Codec, count: CountNode): Codec => { + switch (count.kind) { + case 'fixedCountNode': + return getArrayCodec(item, { size: count.value }); + case 'prefixedCountNode': + return getArrayCodec(item, { size: visit(count.prefix, visitor) as NumberCodec }); + case 'remainderCountNode': + return getArrayCodec(item, { size: 'remainder' }); + case 'sentinelCountNode': { + const sentinel = getConstantBytes(count.sentinel); + return getArrayCodec(item, { size: { __kind: 'sentinel', sentinel, strategy: count.strategy } }); + } + } + }; + + const applyTransform = (codec: Codec, transform: TransformNode): Codec => { + switch (transform.kind) { + case 'fixedSizeTransformNode': + return fixCodecSize(codec, transform.size); + case 'sizePrefixTransformNode': + return addCodecSizePrefix(codec, visit(transform.prefix, visitor) as NumberCodec); + case 'sentinelTransformNode': + return addCodecSentinel(codec, getConstantBytes(transform.sentinel)); + case 'hiddenPrefixTransformNode': + return getHiddenPrefixCodec( + codec, + (transform.prefix ?? []).map(constant => getConstantCodec(getConstantBytes(constant))), + ); + case 'hiddenSuffixTransformNode': + return getHiddenSuffixCodec( + codec, + (transform.suffix ?? []).map(constant => getConstantCodec(getConstantBytes(constant))), + ); + case 'preOffsetTransformNode': { + const { offset } = transform; + switch (transform.strategy) { + case 'padded': + return padLeftCodec(codec, offset); + case 'absolute': + return offsetCodec(codec, { + preOffset: ({ wrapBytes }) => (offset < 0 ? wrapBytes(offset) : offset), + }); + case 'relative': + return offsetCodec(codec, { preOffset: ({ preOffset }) => preOffset + offset }); + } + break; + } + case 'postOffsetTransformNode': { + const { offset } = transform; + switch (transform.strategy) { + case 'padded': + return padRightCodec(codec, offset); + case 'absolute': + return offsetCodec(codec, { + postOffset: ({ wrapBytes }) => (offset < 0 ? wrapBytes(offset) : offset), + }); + case 'preOffset': + return offsetCodec(codec, { postOffset: ({ preOffset }) => preOffset + offset }); + case 'relative': + return offsetCodec(codec, { postOffset: ({ postOffset }) => postOffset + offset }); + } + break; + } + } + return codec; + }; const baseVisitor: Visitor, EncodableNodes['kind']> = { visitAccount(node) { return visit(node.data, this); }, visitAccountLink(node) { - const path = linkables.getPathOrThrow(stack.getPath(node.kind)); - stack.pushPath(path); - const result = visit(getLastNodeFromPath(path), this); - stack.popPath(); - return result; - }, - visitAmountType(node) { - return visit(node.number, this); + return visitLinkedNode(node); }, visitArrayType(node) { - const item = visit(node.item, this); - const size = getSizeFromCountNode(node.count, this); - return getArrayCodec(item, { size }) as Codec; + return getCollectionCodec(visit(node.item, this), node.count) as Codec; }, visitBooleanType(node) { - const size = visit(node.size, this) as NumberCodec; + const size = visit(node.size, this) as FixedSizeNumberCodec; return getBooleanCodec({ size }) as Codec; }, visitBytesType() { - // Note we use a format like `["base64", "someData"]` to encode bytes, - // instead of using `Uint8Arrays` in order to be compatible with JSON. + // Bytes are represented as `[encoding, data]` tuples, e.g. `["base64", "SGVsbG8="]`, + // rather than `Uint8Arrays` in order to be compatible with JSON. return createCodec<[BytesEncoding, string]>({ getSizeFromValue: ([encoding, value]) => { return getCodecFromBytesEncoding(encoding).getSizeFromValue(value); @@ -165,188 +266,88 @@ export function getNodeCodecVisitor( return visit(node.type, this); }, visitDefinedTypeLink(node) { - const path = linkables.getPathOrThrow(stack.getPath(node.kind)); - stack.pushPath(path); - const result = visit(getLastNodeFromPath(path), this); - stack.popPath(); - return result; - }, - visitEnumEmptyVariantType() { - return getUnitCodec() as Codec; - }, - visitEnumStructVariantType(node) { - return visit(node.struct, this); + return visitLinkedNode(node); }, - visitEnumTupleVariantType(node) { - const tupleAsStruct = structTypeNode([structFieldTypeNode({ identifier: 'fields', type: node.tuple })]); - return visit(tupleAsStruct, this); + visitDurationType(node) { + return visit(node.number, this); }, visitEnumType(node) { const size = visit(node.size, this) as NumberCodec; - // All enums are decoded as discriminated unions carrying their wire - // discriminator, e.g. `{ __kind: 'Up', __discriminator: 0 }` or - // `{ __kind: 'Move', __discriminator: 1, x: 10, y: 20 }`. The wire - // byte honors `variant.discriminator`, falling back to the position. const variants = node.variants ?? []; + // An omitted discriminator is the variant's position, not the previous discriminator plus one. const discriminators = variants.map((variant, index) => variant.discriminator ?? index); const variantCodecs = variants.map((variant, index) => { - const kind = pascalCase(variant.identifier); - const discriminator = discriminators[index]; - const payload = getHiddenPrefixCodec(visit(variant, this) as Codec, [ - getConstantCodec(size.encode(discriminator)), - ]); + const __kind = variant.identifier; + const __discriminator = discriminators[index]; + const prefix = getConstantCodec(size.encode(__discriminator)); + const codec = getHiddenPrefixCodec(visit(variant, this), [prefix]); + if (variant.data === undefined) { + return transformCodec( + codec, + (_: EnumVariantValue) => undefined, + (): EnumVariantValue => ({ __discriminator, __kind }), + ); + } return transformCodec( - payload, - (value: unknown) => { - if (typeof value !== 'object' || value === null) return value; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { __discriminator: _d, __kind: _k, ...rest } = value as Record; - return rest; - }, - (decoded: unknown) => ({ - __discriminator: discriminator, - __kind: kind, - ...(typeof decoded === 'object' && decoded !== null ? decoded : {}), - }), + codec, + (value: EnumVariantValue) => value.data, + (data): EnumVariantValue => ({ __discriminator, __kind, data }), ); }); return getUnionCodec( variantCodecs, - value => { - const kind = (value as { __kind?: unknown } | null)?.__kind; - return variants.findIndex(variant => pascalCase(variant.identifier) === kind); - }, - (bytes, offset) => { - const [discriminator] = size.read(bytes, offset); - return discriminators.indexOf(Number(discriminator)); - }, - ) as unknown as Codec; + value => variants.findIndex(variant => variant.identifier === value.__kind), + (bytes, offset) => discriminators.indexOf(Number(size.read(bytes, offset)[0])), + ) as Codec; + }, + visitEnumVariantType(node) { + return node.data ? visit(node.data, this) : (getUnitCodec() as Codec); }, visitEvent(node) { return visit(node.data, this); }, - visitFixedSizeType(node) { - const type = visit(node.type, this); - return fixCodecSize(type, node.size); - }, - visitHiddenPrefixType(node) { - const type = visit(node.type, this); - const constants = (node.prefix ?? []).map(constant => { - const constantCodec = visit(constant.type, this); - const constantValue = visit(constant.value, valueNodeVisitor); - return getConstantCodec(constantCodec.encode(constantValue)); - }); - return getHiddenPrefixCodec(type, constants); + visitFixedPointType(node) { + return visit(node.number, this); }, - visitHiddenSuffixType(node) { - const type = visit(node.type, this); - const constants = (node.suffix ?? []).map(constant => { - const constantCodec = visit(constant.type, this); - const constantValue = visit(constant.value, valueNodeVisitor); - return getConstantCodec(constantCodec.encode(constantValue)); - }); - return getHiddenSuffixCodec(type, constants); + visitFloatType(node) { + return getFloatCodec(node) as Codec; }, visitInstruction(node) { - return visit(structTypeNodeFromInstructionArgumentNodes(node.arguments ?? []), this); - }, - visitInstructionArgument(node) { - return visit(structFieldTypeNodeFromInstructionArgumentNode(node), this); - }, - visitInstructionArgumentLink(node) { - const path = linkables.getPathOrThrow(stack.getPath(node.kind)); - stack.pushPath(path); - const result = visit(getLastNodeFromPath(path), this); - stack.popPath(); - return result; + return node.data ? visit(node.data, this) : (getUnitCodec() as Codec); }, visitInstructionLink(node) { - const path = linkables.getPathOrThrow(stack.getPath(node.kind)); - stack.pushPath(path); - const result = visit(getLastNodeFromPath(path), this); - stack.popPath(); - return result; + return visitLinkedNode(node); + }, + visitIntegerType(node) { + return getIntegerCodec(node) as Codec; }, visitMapType(node) { - const key = visit(node.key, this); - const value = visit(node.value, this); - const size = getSizeFromCountNode(node.count, this); - // Note we transform maps as objects to be compatible with JSON. + const entry = getTupleCodec([visit(node.key, this), visit(node.value, this)]); + // Maps are represented as objects in order to be compatible with JSON. return transformCodec( - getMapCodec(key, value, { size }), - (value: object) => new Map(Object.entries(value)), - (map: Map) => Object.fromEntries(map) as object, + getCollectionCodec(entry as Codec, node.count), + (value: object) => Object.entries(value), + entries => Object.fromEntries(entries as [PropertyKey, unknown][]), ) as Codec; }, - visitNumberType(node) { - return getCodecFromNumberFormat(node.format) as Codec; - }, visitOptionType(node) { const item = visit(node.item, this); const prefix = visit(node.prefix, this) as NumberCodec; if (node.fixed) { assertIsFixedSize(item); - return getOptionCodec(item, { noneValue: 'zeroes', prefix }); - } - return getOptionCodec(item, { prefix }); - }, - visitPostOffsetType(node) { - const type = visit(node.type, this); - switch (node.strategy) { - case 'padded': - return padRightCodec(type, node.offset); - case 'absolute': - return offsetCodec(type, { - postOffset: ({ wrapBytes }) => (node.offset < 0 ? wrapBytes(node.offset) : node.offset), - }); - case 'preOffset': - return offsetCodec(type, { postOffset: ({ preOffset }) => preOffset + node.offset }); - case 'relative': - default: - return offsetCodec(type, { postOffset: ({ postOffset }) => postOffset + node.offset }); - } - }, - visitPreOffsetType(node) { - const type = visit(node.type, this); - switch (node.strategy) { - case 'padded': - return padLeftCodec(type, node.offset); - case 'absolute': - return offsetCodec(type, { - preOffset: ({ wrapBytes }) => (node.offset < 0 ? wrapBytes(node.offset) : node.offset), - }); - case 'relative': - default: - return offsetCodec(type, { preOffset: ({ preOffset }) => preOffset + node.offset }); + return getOptionCodec(item, { noneValue: 'zeroes', prefix }) as Codec; } + return getOptionCodec(item, { prefix }) as Codec; }, visitPublicKeyType() { return fixCodecSize(getBase58Codec(), 32) as Codec; }, visitRemainderOptionType(node) { - const item = visit(node.item, this); - return getOptionCodec(item, { prefix: null }); - }, - visitSentinelType(node) { - const type = visit(node.type, this); - const sentinelCodec = visit(node.sentinel.type, this); - const sentinelValue = visit(node.sentinel.value, valueNodeVisitor); - const sentinelBytes = sentinelCodec.encode(sentinelValue); - return addCodecSentinel(type, sentinelBytes); + return getOptionCodec(visit(node.item, this), { prefix: null }) as Codec; }, visitSetType(node) { - const item = visit(node.item, this); - const size = getSizeFromCountNode(node.count, this); - // Note we use the array codecs since it is compatible with the JSON format. - return getArrayCodec(item, { size }) as Codec; - }, - visitSizePrefixType(node) { - const type = visit(node.type, this); - const prefix = visit(node.prefix, this) as NumberCodec; - return addCodecSizePrefix(type, prefix); - }, - visitSolAmountType(node) { - return visit(node.number, this); + // Sets are represented as arrays in order to be compatible with JSON. + return getCollectionCodec(visit(node.item, this), node.count) as Codec; }, visitStringType(node) { return getCodecFromBytesEncoding(node.encoding) as Codec; @@ -359,26 +360,44 @@ export function getNodeCodecVisitor( return getStructCodec(fields) as Codec; }, visitTupleType(node) { - const items = (node.items ?? []).map(item => visit(item, this)); - return getTupleCodec(items) as Codec; + return getTupleCodec((node.items ?? []).map(item => visit(item, this))) as Codec; }, visitZeroableOptionType(node) { const item = visit(node.item, this); assertIsFixedSize(item); if (node.zeroValue) { - const noneCodec = visit(node.zeroValue.type, this); - const noneValue = visit(node.zeroValue.value, valueNodeVisitor); - const noneBytes = noneCodec.encode(noneValue); - return getOptionCodec(item, { noneValue: noneBytes, prefix: null }); + const noneValue = getConstantBytes(node.zeroValue); + return getOptionCodec(item, { noneValue, prefix: null }) as Codec; } - return getOptionCodec(item, { noneValue: 'zeroes', prefix: null }); + return getOptionCodec(item, { noneValue: 'zeroes', prefix: null }) as Codec; }, }; - const visitor = pipe(baseVisitor, v => recordNodeStackVisitor(v, stack)); + const visitor: Visitor, EncodableNodes['kind']> = pipe( + baseVisitor, + // Layer each type node's transforms, innermost first, on top of its own codec. + // For link nodes, they apply on top of the linked type's own transforms. + v => + interceptVisitor(v, (node, next) => { + const codec = next(node); + if (!isNode(node, TYPE_NODE_KINDS)) return codec; + return (node.transforms ?? []).reduce(applyTransform, codec); + }), + v => recordProvidedScopeVisitor(v, scope), + v => recordNodeStackVisitor(v, stack), + ); return visitor; } +/** Encode a constant value node using its own type, e.g. to get the bytes of a sentinel. */ +export function getConstantValueBytes( + node: ConstantValueNode, + codecVisitor: Visitor, EncodableNodes['kind']>, + valueVisitor: Visitor, +): ReadonlyUint8Array { + return visit(node.type, codecVisitor).encode(visit(node.value, valueVisitor)); +} + function getCodecFromBytesEncoding(encoding: BytesEncoding) { switch (encoding) { case 'base16': @@ -396,50 +415,55 @@ function getCodecFromBytesEncoding(encoding: BytesEncoding) { } } -function getCodecFromNumberFormat(format: NumberFormat) { - switch (format) { +/** Integers always decode as `bigint`s, whatever their size, and encode from `number`s or `bigint`s. */ +function getIntegerCodec(node: IntegerTypeNode): Codec { + const config = { endian: node.endian === 'be' ? Endian.Big : Endian.Little }; + const toBigInt = (codec: Codec) => + transformCodec( + codec, + (value: bigint | number) => value, + value => BigInt(value), + ); + switch (node.format) { case 'u8': - return getU8Codec(); + return toBigInt(getU8Codec()); case 'u16': - return getU16Codec(); + return toBigInt(getU16Codec(config)); case 'u32': - return getU32Codec(); + return toBigInt(getU32Codec(config)); case 'u64': - return getU64Codec(); + return getU64Codec(config); case 'u128': - return getU128Codec(); + return getU128Codec(config); case 'i8': - return getI8Codec(); + return toBigInt(getI8Codec()); case 'i16': - return getI16Codec(); + return toBigInt(getI16Codec(config)); case 'i32': - return getI32Codec(); + return toBigInt(getI32Codec(config)); case 'i64': - return getI64Codec(); + return getI64Codec(config); case 'i128': - return getI128Codec(); - case 'f32': - return getF32Codec(); - case 'f64': - return getF64Codec(); + return getI128Codec(config); case 'shortU16': - return getShortU16Codec(); + return toBigInt(getShortU16Codec()); default: throw new CodamaError(CODAMA_ERROR__UNRECOGNIZED_NUMBER_FORMAT, { - format: format satisfies never, + format: node.format satisfies never, }); } } -function getSizeFromCountNode( - node: CountNode, - visitor: Visitor, -): NumberCodec | number | 'remainder' { - if (isNode(node, 'prefixedCountNode')) { - return visit(node.prefix, visitor) as NumberCodec; - } - if (isNode(node, 'fixedCountNode')) { - return node.value; +function getFloatCodec(node: FloatTypeNode): Codec { + const config = { endian: node.endian === 'be' ? Endian.Big : Endian.Little }; + switch (node.format) { + case 'f32': + return getF32Codec(config); + case 'f64': + return getF64Codec(config); + default: + throw new CodamaError(CODAMA_ERROR__UNRECOGNIZED_NUMBER_FORMAT, { + format: node.format satisfies never, + }); } - return 'remainder'; } diff --git a/packages/dynamic-codecs/src/index.ts b/packages/dynamic-codecs/src/index.ts index ec3060826..3e22c7181 100644 --- a/packages/dynamic-codecs/src/index.ts +++ b/packages/dynamic-codecs/src/index.ts @@ -1,7 +1,7 @@ -import { LinkableDictionary, NodeStack } from '@codama/visitors-core'; +import { LinkableDictionary, NodeStack, ProvidedScope } from '@codama/visitors-core'; import { containsBytes, ReadonlyUint8Array } from '@solana/codecs'; -import { getNodeCodecVisitor } from './codecs'; +import { CodecVisitorOptions, getNodeValueCodecVisitor } from './codecs'; import { getValueNodeVisitor } from './values'; export * from './codecs'; @@ -10,14 +10,23 @@ export * from './values'; export type { ReadonlyUint8Array }; export { containsBytes }; +/** A codec visitor and a value visitor sharing the same stack and scope. */ export type CodecAndValueVisitors = { - codecVisitor: ReturnType; + codecVisitor: ReturnType; valueVisitor: ReturnType; }; -export function getCodecAndValueVisitors(linkables: LinkableDictionary, options: { stack?: NodeStack } = {}) { +/** + * Get a codec visitor and a value visitor sharing the same stack and scope, so values + * can be encoded with the codecs of their types, e.g. to compare discriminators. + */ +export function getCodecAndValueVisitors( + linkables: LinkableDictionary, + options: CodecVisitorOptions & { scope?: ProvidedScope; stack?: NodeStack } = {}, +): CodecAndValueVisitors { const stack = options.stack ?? new NodeStack(); - const codecVisitor = getNodeCodecVisitor(linkables, { stack }); - const valueVisitor = getValueNodeVisitor(linkables, { codecVisitorFactory: () => codecVisitor, stack }); + const scope = options.scope ?? new ProvidedScope(); + const codecVisitor = getNodeValueCodecVisitor(linkables, { ...options, scope, stack }); + const valueVisitor = getValueNodeVisitor(linkables, { codecVisitorFactory: () => codecVisitor, scope, stack }); return { codecVisitor, valueVisitor }; } diff --git a/packages/dynamic-codecs/src/values.ts b/packages/dynamic-codecs/src/values.ts index 7ffa27bf0..d0e1707db 100644 --- a/packages/dynamic-codecs/src/values.ts +++ b/packages/dynamic-codecs/src/values.ts @@ -1,27 +1,45 @@ +import { CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND, CodamaError } from '@codama/errors'; +import { assertIsNode, bytesTypeNode, ValueNode, VALUE_NODE_KINDS } from '@codama/nodes'; import { - CODAMA_ERROR__DYNAMIC_CLIENT__INVARIANT_VIOLATION, - CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND, - CodamaError, -} from '@codama/errors'; -import { pascalCase } from '@codama/fragments/casing'; -import { assertIsNode, bytesTypeNode, isNode, ValueNode } from '@codama/nodes'; -import { LinkableDictionary, NodeStack, pipe, recordNodeStackVisitor, visit, Visitor } from '@codama/visitors-core'; + LinkableDictionary, + NodeStack, + pipe, + ProvidedScope, + recordNodeStackVisitor, + visit, + Visitor, +} from '@codama/visitors-core'; -import { CodecVisitorOptions, getNodeCodecVisitor } from './codecs'; +import { CodecVisitorOptions, getConstantValueBytes, getNodeValueCodecVisitor } from './codecs'; +/** + * A visitor that returns the raw JavaScript value of the visited value node, in the + * same format as the codecs of {@link getNodeValueCodec}, e.g. `integerValueNode('42')` + * returns `42n`. + * + * The `stack` must hold the ancestors of the visited node to resolve enum links, and the + * `scope` must hold the `provides` of its enclosing instructions to resolve injected values. + */ export function getValueNodeVisitor( linkables: LinkableDictionary, options: { - codecVisitorFactory?: () => ReturnType; + codecVisitorFactory?: () => ReturnType; codecVisitorOptions?: CodecVisitorOptions; + scope?: ProvidedScope; stack?: NodeStack; } = {}, ): Visitor { const stack = options.stack ?? new NodeStack(); - let cachedCodecVisitor: ReturnType | null = null; + const scope = options.scope ?? new ProvidedScope(); + let cachedCodecVisitor: ReturnType | null = null; const codecVisitorFactory = options.codecVisitorFactory ?? - (() => (cachedCodecVisitor ??= getNodeCodecVisitor(linkables, { stack, ...options.codecVisitorOptions }))); + (() => + (cachedCodecVisitor ??= getNodeValueCodecVisitor(linkables, { + ...options.codecVisitorOptions, + scope, + stack, + }))); const baseVisitor: Visitor = { visitArrayValue(node) { @@ -34,11 +52,9 @@ export function getValueNodeVisitor( return [node.encoding, node.data]; }, visitConstantValue(node) { - const codec = visit(node.type, codecVisitorFactory()); - const value = visit(node.value, this); - const bytes = codec.encode(value); - const bytesCodec = visit(bytesTypeNode(), codecVisitorFactory()); - return bytesCodec.decode(bytes); + const codecVisitor = codecVisitorFactory(); + const bytes = getConstantValueBytes(node, codecVisitor, this); + return visit(bytesTypeNode(), codecVisitor).decode(bytes); }, visitEnumValue(node) { const enumType = linkables.getOrThrow([...stack.getPath(node.kind), node.enum]).type; @@ -52,44 +68,27 @@ export function getValueNodeVisitor( variant: node.variant, }); } - const variant = variants[variantIndex]; - const kind = { __discriminator: variant.discriminator ?? variantIndex, __kind: pascalCase(node.variant) }; - if (isNode(variant, 'enumEmptyVariantTypeNode')) return kind; - if (isNode(variant, 'enumStructVariantTypeNode') && !!node.value) { - const value = visit(node.value, this) as object; - return { ...kind, ...value }; - } - if (isNode(variant, 'enumTupleVariantTypeNode') && !!node.value) { - const fields = visit(node.value, this); - return { ...kind, fields }; - } - return kind; + const __discriminator = variants[variantIndex].discriminator ?? variantIndex; + const value = { __discriminator, __kind: node.variant }; + return node.value === undefined ? value : { ...value, data: visit(node.value, this) }; + }, + visitFloatValue(node) { + return Number(node.value); }, visitInjectedValue(node) { - // `injectedValueNode` is a placeholder resolved by the provide/inject graph - // at instruction-build / presentation time. By codec time the resolution - // pass should have replaced it with a concrete value; reaching this layer - // unresolved indicates an upstream pipeline bug, not a value this visitor - // can decode without ancestor context. - throw new CodamaError(CODAMA_ERROR__DYNAMIC_CLIENT__INVARIANT_VIOLATION, { - message: `injectedValueNode (key "${node.key}") reached codec evaluation; the provide/inject resolution pass must run first.`, - }); + return visit(scope.resolveOrThrow(node, { kinds: VALUE_NODE_KINDS }), this); + }, + visitIntegerValue(node) { + return BigInt(node.value); }, visitMapValue(node) { return Object.fromEntries( - (node.entries ?? []).map(entry => { - const key = visit(entry.key, this); - const value = visit(entry.value, this); - return [key, value]; - }), + (node.entries ?? []).map(entry => [visit(entry.key, this), visit(entry.value, this)] as const), ) as unknown; }, visitNoneValue() { return { __option: 'None' }; }, - visitNumberValue(node) { - return node.number; - }, visitPublicKeyValue(node) { return node.publicKey; }, @@ -97,20 +96,13 @@ export function getValueNodeVisitor( return (node.items ?? []).map(item => visit(item, this)); }, visitSomeValue(node) { - const value = visit(node.value, this); - return { __option: 'Some', value }; + return { __option: 'Some', value: visit(node.value, this) }; }, visitStringValue(node) { return node.string; }, visitStructValue(node) { - return Object.fromEntries( - (node.fields ?? []).map(field => { - const name = field.identifier; - const value = visit(field.value, this); - return [name, value]; - }), - ); + return Object.fromEntries((node.fields ?? []).map(field => [field.identifier, visit(field.value, this)])); }, visitTupleValue(node) { return (node.items ?? []).map(item => visit(item, this)); diff --git a/packages/dynamic-codecs/test/codecs/AccountLinkNode.test.ts b/packages/dynamic-codecs/test/codecs/AccountLinkNode.test.ts new file mode 100644 index 000000000..76134afb1 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/AccountLinkNode.test.ts @@ -0,0 +1,22 @@ +import { accountLinkNode, accountNode, integerTypeNode, programNode, rootNode } from '@codama/nodes'; +import { getRecordLinkablesVisitor, LinkableDictionary, NodeStack, visit } from '@codama/visitors-core'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodecVisitor } from '../../src'; +import { hex } from '../_setup'; + +test('it resolves the codec of account link nodes', () => { + // Given an account and a link node pointing to it. + const account = accountNode({ data: integerTypeNode('u32'), identifier: 'counter' }); + const root = rootNode(programNode({ accounts: [account], identifier: 'myProgram', publicKey: '1111' })); + const linkables = new LinkableDictionary(); + visit(root, getRecordLinkablesVisitor(linkables)); + + // When we get the codec of the link node from within the program. + const stack = new NodeStack([root, root.program]); + const codec = visit(accountLinkNode('counter'), getNodeValueCodecVisitor(linkables, { stack })); + + // Then we expect the codec of the linked account. + expect(codec.encode(42)).toStrictEqual(hex('2a000000')); + expect(codec.decode(hex('2a000000'))).toBe(42n); +}); diff --git a/packages/dynamic-codecs/test/codecs/AccountNode.test.ts b/packages/dynamic-codecs/test/codecs/AccountNode.test.ts index 5a01e40e4..4f24912c7 100644 --- a/packages/dynamic-codecs/test/codecs/AccountNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/AccountNode.test.ts @@ -1,21 +1,16 @@ -import { accountNode, numberTypeNode, structFieldTypeNode, structTypeNode } from '@codama/nodes'; +import { accountNode, integerTypeNode, structFieldTypeNode, structTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it delegates to the underlying data node', () => { - const codec = getNodeCodec([ + const codec = getNodeValueCodec([ accountNode({ - data: structTypeNode([ - structFieldTypeNode({ - name: 'foo', - type: numberTypeNode('u32'), - }), - ]), - name: 'myAccount', + data: structTypeNode([structFieldTypeNode({ identifier: 'foo', type: integerTypeNode('u32') })]), + identifier: 'myAccount', }), ]); expect(codec.encode({ foo: 42 })).toStrictEqual(hex('2a000000')); - expect(codec.decode(hex('2a000000'))).toStrictEqual({ foo: 42 }); + expect(codec.decode(hex('2a000000'))).toStrictEqual({ foo: 42n }); }); diff --git a/packages/dynamic-codecs/test/codecs/ArrayTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/ArrayTypeNode.test.ts index 3d913f8b9..bdb0413c0 100644 --- a/packages/dynamic-codecs/test/codecs/ArrayTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/ArrayTypeNode.test.ts @@ -1,23 +1,23 @@ -import { arrayTypeNode, fixedCountNode, numberTypeNode, prefixedCountNode, remainderCountNode } from '@codama/nodes'; +import { arrayTypeNode, fixedCountNode, integerTypeNode, prefixedCountNode, remainderCountNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it decodes prefixed arrays', () => { - const codec = getNodeCodec([arrayTypeNode(numberTypeNode('u16'), prefixedCountNode(numberTypeNode('u32')))]); + const codec = getNodeValueCodec([arrayTypeNode(integerTypeNode('u16'), prefixedCountNode(integerTypeNode('u32')))]); expect(codec.encode([42, 99, 650])).toStrictEqual(hex('030000002a0063008a02')); - expect(codec.decode(hex('030000002a0063008a02'))).toStrictEqual([42, 99, 650]); + expect(codec.decode(hex('030000002a0063008a02'))).toStrictEqual([42n, 99n, 650n]); }); test('it decodes fixed arrays', () => { - const codec = getNodeCodec([arrayTypeNode(numberTypeNode('u16'), fixedCountNode(3))]); + const codec = getNodeValueCodec([arrayTypeNode(integerTypeNode('u16'), fixedCountNode(3))]); expect(codec.encode([42, 99, 650])).toStrictEqual(hex('2a0063008a02')); - expect(codec.decode(hex('2a0063008a02'))).toStrictEqual([42, 99, 650]); + expect(codec.decode(hex('2a0063008a02'))).toStrictEqual([42n, 99n, 650n]); }); test('it decodes remainder arrays', () => { - const codec = getNodeCodec([arrayTypeNode(numberTypeNode('u16'), remainderCountNode())]); + const codec = getNodeValueCodec([arrayTypeNode(integerTypeNode('u16'), remainderCountNode())]); expect(codec.encode([42, 99, 650])).toStrictEqual(hex('2a0063008a02')); - expect(codec.decode(hex('2a0063008a02'))).toStrictEqual([42, 99, 650]); + expect(codec.decode(hex('2a0063008a02'))).toStrictEqual([42n, 99n, 650n]); }); diff --git a/packages/dynamic-codecs/test/codecs/BooleanTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/BooleanTypeNode.test.ts index dbe671313..813a99860 100644 --- a/packages/dynamic-codecs/test/codecs/BooleanTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/BooleanTypeNode.test.ts @@ -1,11 +1,11 @@ -import { booleanTypeNode, numberTypeNode } from '@codama/nodes'; +import { booleanTypeNode, integerTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('default', () => { - const codec = getNodeCodec([booleanTypeNode()]); + const codec = getNodeValueCodec([booleanTypeNode()]); expect(codec.encode(true)).toStrictEqual(hex('01')); expect(codec.decode(hex('01'))).toBe(true); expect(codec.encode(false)).toStrictEqual(hex('00')); @@ -13,9 +13,15 @@ test('default', () => { }); test('custom number', () => { - const codec = getNodeCodec([booleanTypeNode(numberTypeNode('u32'))]); + const codec = getNodeValueCodec([booleanTypeNode({ size: integerTypeNode('u32') })]); expect(codec.encode(true)).toStrictEqual(hex('01000000')); expect(codec.decode(hex('01000000'))).toBe(true); expect(codec.encode(false)).toStrictEqual(hex('00000000')); expect(codec.decode(hex('00000000'))).toBe(false); }); + +test('big-endian number', () => { + const codec = getNodeValueCodec([booleanTypeNode({ size: integerTypeNode('u16', { endian: 'be' }) })]); + expect(codec.encode(true)).toStrictEqual(hex('0001')); + expect(codec.decode(hex('0001'))).toBe(true); +}); diff --git a/packages/dynamic-codecs/test/codecs/BytesTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/BytesTypeNode.test.ts index 5a114728b..626a1d707 100644 --- a/packages/dynamic-codecs/test/codecs/BytesTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/BytesTypeNode.test.ts @@ -1,23 +1,23 @@ import { bytesTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it uses base64 encoding by default', () => { - const codec = getNodeCodec([bytesTypeNode()]); + const codec = getNodeValueCodec([bytesTypeNode()]); expect(codec.encode(['base64', 'HelloWorld++'])).toStrictEqual(hex('1de965a16a2b95dfbe')); expect(codec.decode(hex('1de965a16a2b95dfbe'))).toStrictEqual(['base64', 'HelloWorld++']); }); test('it can use a custom default encoding', () => { - const codec = getNodeCodec([bytesTypeNode()], { bytesEncoding: 'base16' }); + const codec = getNodeValueCodec([bytesTypeNode()], { bytesEncoding: 'base16' }); expect(codec.encode(['base16', 'deadb0d1e5'])).toStrictEqual(hex('deadb0d1e5')); expect(codec.decode(hex('deadb0d1e5'))).toStrictEqual(['base16', 'deadb0d1e5']); }); test('the first tuple item is always used when encoding the data', () => { - const codec = getNodeCodec([bytesTypeNode()], { bytesEncoding: 'base64' }); + const codec = getNodeValueCodec([bytesTypeNode()], { bytesEncoding: 'base64' }); expect(codec.encode(['base16', 'deadb0d1e5'])).toStrictEqual(hex('deadb0d1e5')); expect(codec.decode(hex('deadb0d1e5'))).toStrictEqual(['base64', '3q2w0eU=']); }); diff --git a/packages/dynamic-codecs/test/codecs/DateTimeTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/DateTimeTypeNode.test.ts new file mode 100644 index 000000000..eab238524 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/DateTimeTypeNode.test.ts @@ -0,0 +1,11 @@ +import { dateTimeTypeNode, integerTypeNode } from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../src'; +import { hex } from '../_setup'; + +test('it encodes date-times as their raw integer', () => { + const codec = getNodeValueCodec([dateTimeTypeNode(integerTypeNode('i64'))]); + expect(codec.encode(1_700_000_000n)).toStrictEqual(hex('00f1536500000000')); + expect(codec.decode(hex('00f1536500000000'))).toBe(1_700_000_000n); +}); diff --git a/packages/dynamic-codecs/test/codecs/DefinedTypeLinkNode.test.ts b/packages/dynamic-codecs/test/codecs/DefinedTypeLinkNode.test.ts index ada8b31ea..9597e2b4e 100644 --- a/packages/dynamic-codecs/test/codecs/DefinedTypeLinkNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/DefinedTypeLinkNode.test.ts @@ -1,30 +1,34 @@ +import { CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, CodamaError } from '@codama/errors'; import { + constantValueNode, definedTypeLinkNode, definedTypeNode, - numberTypeNode, + fixedSizeTransformNode, + hiddenPrefixTransformNode, + injectedValueNode, + integerTypeNode, programLinkNode, programNode, rootNode, + sizePrefixTransformNode, + stringTypeNode, } from '@codama/nodes'; +import { getRecordLinkablesVisitor, LinkableDictionary, NodeStack, visit } from '@codama/visitors-core'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec, getNodeValueCodecVisitor } from '../../src'; import { hex } from '../_setup'; test('it resolves the codec of defined type link nodes', () => { // Given an existing defined type and a LinkNode pointing to it. - const slotType = definedTypeNode({ name: 'slot', type: numberTypeNode('u64') }); - const lastSlotType = definedTypeNode({ name: 'lastSlot', type: definedTypeLinkNode('slot') }); + const slotType = definedTypeNode({ identifier: 'slot', type: integerTypeNode('u64') }); + const lastSlotType = definedTypeNode({ identifier: 'lastSlot', type: definedTypeLinkNode('slot') }); const root = rootNode( - programNode({ - definedTypes: [slotType, lastSlotType], - name: 'myProgram', - publicKey: '1111', - }), + programNode({ definedTypes: [slotType, lastSlotType], identifier: 'myProgram', publicKey: '1111' }), ); // When we get the codec for the defined type pointing to another defined type. - const codec = getNodeCodec([root, root.program, lastSlotType]); + const codec = getNodeValueCodec([root, root.program, lastSlotType]); // Then we expect the codec to match the linked defined type. expect(codec.encode(42)).toStrictEqual(hex('2a00000000000000')); @@ -35,28 +39,86 @@ test('it follows linked nodes using the correct paths', () => { // Given two link nodes designed so that the path would // fail if we did not save and restored linked paths. const typeA = definedTypeNode({ - name: 'typeA', - type: definedTypeLinkNode('typeB1', programLinkNode('programB')), - }); - const programA = programNode({ - definedTypes: [typeA], - name: 'programA', - publicKey: '1111', + identifier: 'typeA', + type: definedTypeLinkNode('typeB1', { program: programLinkNode('programB') }), }); + const programA = programNode({ definedTypes: [typeA], identifier: 'programA', publicKey: '1111' }); const programB = programNode({ definedTypes: [ - definedTypeNode({ name: 'typeB1', type: definedTypeLinkNode('typeB2') }), - definedTypeNode({ name: 'typeB2', type: numberTypeNode('u64') }), + definedTypeNode({ identifier: 'typeB1', type: definedTypeLinkNode('typeB2') }), + definedTypeNode({ identifier: 'typeB2', type: integerTypeNode('u64') }), ], - name: 'programB', + identifier: 'programB', publicKey: '2222', }); - const root = rootNode(programA, [programB]); + const root = rootNode(programA, { additionalPrograms: [programB] }); // When we get the codec for the defined type in programA. - const codec = getNodeCodec([root, programA, typeA]); + const codec = getNodeValueCodec([root, programA, typeA]); // Then we expect the links in programB to be resolved correctly. expect(codec.encode(42)).toStrictEqual(hex('2a00000000000000')); expect(codec.decode(hex('2a00000000000000'))).toBe(42n); }); + +test('it layers the transforms of the link on top of the linked type', () => { + // Given a fixed-size string type and a link to it that adds a size prefix. + const nameType = definedTypeNode({ + identifier: 'name', + type: stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(4)] }), + }); + const link = definedTypeLinkNode('name', { transforms: [sizePrefixTransformNode(integerTypeNode('u8'))] }); + const wrapperType = definedTypeNode({ identifier: 'wrapper', type: link }); + const root = rootNode( + programNode({ definedTypes: [nameType, wrapperType], identifier: 'myProgram', publicKey: '1111' }), + ); + + // When we get the codec for the type using the link. + const codec = getNodeValueCodec([root, root.program, wrapperType]); + + // Then the size prefix wraps the fixed-size string. + expect(codec.encode('abc')).toStrictEqual(hex('0461626300')); + expect(codec.decode(hex('0461626300'))).toBe('abc'); +}); + +test('it restores the node stack when a linked type throws', () => { + // Given a type in programB whose bytes depend on an injected value that nothing provides. + const tag = injectedValueNode({ key: 'tag' }); + const programB = programNode({ + definedTypes: [ + definedTypeNode({ + identifier: 'tagged', + type: integerTypeNode('u16', { + transforms: [hiddenPrefixTransformNode([constantValueNode(integerTypeNode('u8'), tag)])], + }), + }), + ], + identifier: 'programB', + publicKey: '2222', + }); + + // And a type that only exists in programA. + const programA = programNode({ + definedTypes: [definedTypeNode({ identifier: 'amount', type: integerTypeNode('u32') })], + identifier: 'programA', + publicKey: '1111', + }); + const root = rootNode(programA, { additionalPrograms: [programB] }); + const linkables = new LinkableDictionary(); + visit(root, getRecordLinkablesVisitor(linkables)); + + // And a codec visitor reused from within programA. + const stack = new NodeStack([root, programA]); + const visitor = getNodeValueCodecVisitor(linkables, { stack }); + + // When visiting a link into programB throws. + const taggedLink = definedTypeLinkNode('tagged', { program: programLinkNode('programB') }); + expect(() => visit(taggedLink, visitor)).toThrow( + new CodamaError(CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, { injectedValue: tag, key: tag.key }), + ); + + // Then the stack is back within programA, so later links still resolve there. + expect(stack.getPath()).toStrictEqual([root, programA]); + const codec = visit(definedTypeLinkNode('amount'), visitor); + expect(codec.encode(42)).toStrictEqual(hex('2a000000')); +}); diff --git a/packages/dynamic-codecs/test/codecs/DefinedTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/DefinedTypeNode.test.ts index 883081f0d..2f23b3194 100644 --- a/packages/dynamic-codecs/test/codecs/DefinedTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/DefinedTypeNode.test.ts @@ -1,11 +1,92 @@ -import { definedTypeNode, numberTypeNode } from '@codama/nodes'; +import { CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, CodamaError } from '@codama/errors'; +import { + constantValueNode, + definedTypeLinkNode, + definedTypeNode, + hiddenPrefixTransformNode, + injectedValueNode, + instructionNode, + integerTypeNode, + programNode, + providedNode, + integerValueNode, + rootNode, + structFieldTypeNode, + structTypeNode, +} from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it delegates to the underlying type node', () => { - const codec = getNodeCodec([definedTypeNode({ name: 'foo', type: numberTypeNode('u32') })]); + const codec = getNodeValueCodec([definedTypeNode({ identifier: 'foo', type: integerTypeNode('u32') })]); expect(codec.encode(42)).toStrictEqual(hex('2a000000')); - expect(codec.decode(hex('2a000000'))).toBe(42); + expect(codec.decode(hex('2a000000'))).toBe(42n); +}); + +test('it ignores injected default values when the type stands on its own', () => { + // Given a defined type whose field defaults to an injected value that nothing provides. + const definedType = definedTypeNode({ + identifier: 'transfer', + type: structTypeNode([ + structFieldTypeNode({ + defaultValue: injectedValueNode({ key: 'decimals' }), + identifier: 'decimals', + type: integerTypeNode('u8'), + }), + ]), + }); + + // Then its codec can still be created, since default values are not part of the bytes. + const codec = getNodeValueCodec([definedType]); + expect(codec.encode({ decimals: 6 })).toStrictEqual(hex('06')); +}); + +test('it throws when the bytes of a standalone type depend on an injected value', () => { + // Given a defined type whose hidden prefix injects a value that nothing provides. + const tag = injectedValueNode({ key: 'tag' }); + const definedType = definedTypeNode({ + identifier: 'tagged', + type: integerTypeNode('u16', { + transforms: [hiddenPrefixTransformNode([constantValueNode(integerTypeNode('u8'), tag)])], + }), + }); + + // Then creating its codec throws. + expect(() => getNodeValueCodec([definedType])).toThrow( + new CodamaError(CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, { injectedValue: tag, key: tag.key }), + ); +}); + +test('it resolves injected values from the instruction using the type', () => { + // Given the same defined type, linked from an instruction that provides the value. + const definedType = definedTypeNode({ + identifier: 'tagged', + type: integerTypeNode('u16', { + transforms: [ + hiddenPrefixTransformNode([ + constantValueNode(integerTypeNode('u8'), injectedValueNode({ key: 'tag' })), + ]), + ], + }), + }); + const instruction = instructionNode({ + data: definedTypeLinkNode('tagged'), + identifier: 'myInstruction', + provides: [providedNode('tag', integerValueNode('7'))], + }); + const root = rootNode( + programNode({ + definedTypes: [definedType], + identifier: 'myProgram', + instructions: [instruction], + publicKey: '1111', + }), + ); + + // Then the instruction's codec resolves the injection. + const codec = getNodeValueCodec([root, root.program, instruction]); + expect(codec.encode(42)).toStrictEqual(hex('072a00')); + expect(codec.decode(hex('072a00'))).toBe(42n); }); diff --git a/packages/dynamic-codecs/test/codecs/DurationTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/DurationTypeNode.test.ts new file mode 100644 index 000000000..6c726963f --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/DurationTypeNode.test.ts @@ -0,0 +1,11 @@ +import { durationTypeNode, integerTypeNode } from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../src'; +import { hex } from '../_setup'; + +test('it encodes durations as their raw integer', () => { + const codec = getNodeValueCodec([durationTypeNode(integerTypeNode('u32'), { ticksPerSecond: 1000 })]); + expect(codec.encode(1500)).toStrictEqual(hex('dc050000')); + expect(codec.decode(hex('dc050000'))).toBe(1500n); +}); diff --git a/packages/dynamic-codecs/test/codecs/EnumTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/EnumTypeNode.test.ts index 8fe25f947..fd7ff69fe 100644 --- a/packages/dynamic-codecs/test/codecs/EnumTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/EnumTypeNode.test.ts @@ -1,10 +1,8 @@ import { - enumEmptyVariantTypeNode, - enumStructVariantTypeNode, - enumTupleVariantTypeNode, enumTypeNode, - fixedSizeTypeNode, - numberTypeNode, + enumVariantTypeNode, + fixedSizeTransformNode, + integerTypeNode, stringTypeNode, structFieldTypeNode, structTypeNode, @@ -12,126 +10,124 @@ import { } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it encodes scalar enums as discriminated unions', () => { - const codec = getNodeCodec([enumTypeNode([enumEmptyVariantTypeNode('up'), enumEmptyVariantTypeNode('down')])]); - expect(codec.encode({ __kind: 'Up' })).toStrictEqual(hex('00')); - expect(codec.decode(hex('00'))).toStrictEqual({ __discriminator: 0, __kind: 'Up' }); - expect(codec.encode({ __kind: 'Down' })).toStrictEqual(hex('01')); - expect(codec.decode(hex('01'))).toStrictEqual({ __discriminator: 1, __kind: 'Down' }); + const codec = getNodeValueCodec([enumTypeNode([enumVariantTypeNode('up'), enumVariantTypeNode('down')])]); + expect(codec.encode({ __kind: 'up' })).toStrictEqual(hex('00')); + expect(codec.decode(hex('00'))).toStrictEqual({ __discriminator: 0, __kind: 'up' }); + expect(codec.encode({ __kind: 'down' })).toStrictEqual(hex('01')); + expect(codec.decode(hex('01'))).toStrictEqual({ __discriminator: 1, __kind: 'down' }); +}); + +test('it uses the raw variant identifiers', () => { + const codec = getNodeValueCodec([enumTypeNode([enumVariantTypeNode('my_variant')])]); + expect(codec.encode({ __kind: 'my_variant' })).toStrictEqual(hex('00')); + expect(codec.decode(hex('00'))).toStrictEqual({ __discriminator: 0, __kind: 'my_variant' }); }); test('it encodes scalar enums with custom sizes', () => { - const codec = getNodeCodec([ - enumTypeNode([enumEmptyVariantTypeNode('up'), enumEmptyVariantTypeNode('down')], { - size: numberTypeNode('u16'), + const codec = getNodeValueCodec([ + enumTypeNode([enumVariantTypeNode('up'), enumVariantTypeNode('down')], { size: integerTypeNode('u16') }), + ]); + expect(codec.encode({ __kind: 'up' })).toStrictEqual(hex('0000')); + expect(codec.decode(hex('0000'))).toStrictEqual({ __discriminator: 0, __kind: 'up' }); + expect(codec.encode({ __kind: 'down' })).toStrictEqual(hex('0100')); + expect(codec.decode(hex('0100'))).toStrictEqual({ __discriminator: 1, __kind: 'down' }); +}); + +test('it encodes enums with big-endian sizes', () => { + const codec = getNodeValueCodec([ + enumTypeNode([enumVariantTypeNode('up'), enumVariantTypeNode('down')], { + size: integerTypeNode('u16', { endian: 'be' }), }), ]); - expect(codec.encode({ __kind: 'Up' })).toStrictEqual(hex('0000')); - expect(codec.decode(hex('0000'))).toStrictEqual({ __discriminator: 0, __kind: 'Up' }); - expect(codec.encode({ __kind: 'Down' })).toStrictEqual(hex('0100')); - expect(codec.decode(hex('0100'))).toStrictEqual({ __discriminator: 1, __kind: 'Down' }); + expect(codec.encode({ __kind: 'down' })).toStrictEqual(hex('0001')); + expect(codec.decode(hex('0001'))).toStrictEqual({ __discriminator: 1, __kind: 'down' }); }); -test('it decodes empty variants the same way in scalar and data enums', () => { - const scalar = getNodeCodec([enumTypeNode([enumEmptyVariantTypeNode('quit'), enumEmptyVariantTypeNode('stay')])]); - const data = getNodeCodec([ +test('it decodes variants without data the same way in scalar and data enums', () => { + const scalar = getNodeValueCodec([enumTypeNode([enumVariantTypeNode('quit'), enumVariantTypeNode('stay')])]); + const data = getNodeValueCodec([ enumTypeNode([ - enumEmptyVariantTypeNode('quit'), - enumStructVariantTypeNode( - 'move', - structTypeNode([structFieldTypeNode({ name: 'x', type: numberTypeNode('u8') })]), - ), + enumVariantTypeNode('quit'), + enumVariantTypeNode('move', { + data: structTypeNode([structFieldTypeNode({ identifier: 'x', type: integerTypeNode('u8') })]), + }), ]), ]); - expect(scalar.decode(hex('00'))).toStrictEqual({ __discriminator: 0, __kind: 'Quit' }); - expect(data.decode(hex('00'))).toStrictEqual({ __discriminator: 0, __kind: 'Quit' }); + expect(scalar.decode(hex('00'))).toStrictEqual({ __discriminator: 0, __kind: 'quit' }); + expect(data.decode(hex('00'))).toStrictEqual({ __discriminator: 0, __kind: 'quit' }); }); test('it encodes data enums', () => { - const codec = getNodeCodec([ + const codec = getNodeValueCodec([ enumTypeNode([ - enumEmptyVariantTypeNode('quit'), - enumTupleVariantTypeNode('write', tupleTypeNode([fixedSizeTypeNode(stringTypeNode('utf8'), 5)])), - enumStructVariantTypeNode( - 'move', - structTypeNode([ - structFieldTypeNode({ name: 'x', type: numberTypeNode('u8') }), - structFieldTypeNode({ name: 'y', type: numberTypeNode('u8') }), + enumVariantTypeNode('quit'), + enumVariantTypeNode('write', { + data: tupleTypeNode([stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(5)] })]), + }), + enumVariantTypeNode('move', { + data: structTypeNode([ + structFieldTypeNode({ identifier: 'x', type: integerTypeNode('u8') }), + structFieldTypeNode({ identifier: 'y', type: integerTypeNode('u8') }), ]), - ), + }), + enumVariantTypeNode('amount', { data: integerTypeNode('u32') }), ]), ]); - const quitVariant = { __kind: 'Quit' }; - expect(codec.encode(quitVariant)).toStrictEqual(hex('00')); - expect(codec.decode(hex('00'))).toStrictEqual({ __discriminator: 0, ...quitVariant }); - const writeVariant = { __kind: 'Write', fields: ['Hello'] }; - expect(codec.encode(writeVariant)).toStrictEqual(hex('0148656c6c6f')); - expect(codec.decode(hex('0148656c6c6f'))).toStrictEqual({ __discriminator: 1, ...writeVariant }); - const moveVariant = { __kind: 'Move', x: 10, y: 20 }; - expect(codec.encode(moveVariant)).toStrictEqual(hex('020a14')); - expect(codec.decode(hex('020a14'))).toStrictEqual({ __discriminator: 2, ...moveVariant }); + expect(codec.encode({ __kind: 'quit' })).toStrictEqual(hex('00')); + expect(codec.decode(hex('00'))).toStrictEqual({ __discriminator: 0, __kind: 'quit' }); + expect(codec.encode({ __kind: 'write', data: ['Hello'] })).toStrictEqual(hex('0148656c6c6f')); + expect(codec.decode(hex('0148656c6c6f'))).toStrictEqual({ __discriminator: 1, __kind: 'write', data: ['Hello'] }); + expect(codec.encode({ __kind: 'move', data: { x: 10, y: 20 } })).toStrictEqual(hex('020a14')); + expect(codec.decode(hex('020a14'))).toStrictEqual({ + __discriminator: 2, + __kind: 'move', + data: { x: 10n, y: 20n }, + }); + expect(codec.encode({ __kind: 'amount', data: 42 })).toStrictEqual(hex('032a000000')); + expect(codec.decode(hex('032a000000'))).toStrictEqual({ __discriminator: 3, __kind: 'amount', data: 42n }); }); -test('it encodes data enums with custom sizes', () => { - const codec = getNodeCodec([ - enumTypeNode( - [ - enumEmptyVariantTypeNode('quit'), - enumTupleVariantTypeNode('write', tupleTypeNode([fixedSizeTypeNode(stringTypeNode('utf8'), 5)])), - ], - { size: numberTypeNode('u16') }, - ), - ]); - const quitVariant = { __kind: 'Quit' }; - expect(codec.encode(quitVariant)).toStrictEqual(hex('0000')); - expect(codec.decode(hex('0000'))).toStrictEqual({ __discriminator: 0, ...quitVariant }); - const writeVariant = { __kind: 'Write', fields: ['Hello'] }; - expect(codec.encode(writeVariant)).toStrictEqual(hex('010048656c6c6f')); - expect(codec.decode(hex('010048656c6c6f'))).toStrictEqual({ __discriminator: 1, ...writeVariant }); -}); - -test('it honors custom variant discriminators on the wire', () => { - const codec = getNodeCodec([ +test('it honours custom variant discriminators on the wire', () => { + const codec = getNodeValueCodec([ enumTypeNode([ - enumEmptyVariantTypeNode('info', 10), - enumEmptyVariantTypeNode('warning', 20), - enumStructVariantTypeNode( - 'critical', - structTypeNode([structFieldTypeNode({ name: 'code', type: numberTypeNode('u8') })]), - 30, - ), + enumVariantTypeNode('info', { discriminator: 10 }), + enumVariantTypeNode('warning', { discriminator: 20 }), + enumVariantTypeNode('critical', { data: integerTypeNode('u8'), discriminator: 30 }), ]), ]); - expect(codec.encode({ __kind: 'Info' })).toStrictEqual(hex('0a')); - expect(codec.decode(hex('0a'))).toStrictEqual({ __discriminator: 10, __kind: 'Info' }); - expect(codec.encode({ __kind: 'Critical', code: 7 })).toStrictEqual(hex('1e07')); - expect(codec.decode(hex('1e07'))).toStrictEqual({ __discriminator: 30, __kind: 'Critical', code: 7 }); + expect(codec.encode({ __kind: 'info' })).toStrictEqual(hex('0a')); + expect(codec.decode(hex('0a'))).toStrictEqual({ __discriminator: 10, __kind: 'info' }); + expect(codec.encode({ __kind: 'critical', data: 7 })).toStrictEqual(hex('1e07')); + expect(codec.decode(hex('1e07'))).toStrictEqual({ __discriminator: 30, __kind: 'critical', data: 7n }); }); test('it re-encodes its own decoded output', () => { - const codec = getNodeCodec([ - enumTypeNode([enumEmptyVariantTypeNode('up', 5), enumEmptyVariantTypeNode('down', 9)]), + const codec = getNodeValueCodec([ + enumTypeNode([ + enumVariantTypeNode('up', { discriminator: 5 }), + enumVariantTypeNode('down', { data: integerTypeNode('u8'), discriminator: 9 }), + ]), ]); - const decoded = codec.decode(hex('09')); - expect(codec.encode(decoded)).toStrictEqual(hex('09')); + expect(codec.encode(codec.decode(hex('05')))).toStrictEqual(hex('05')); + expect(codec.encode(codec.decode(hex('092a')))).toStrictEqual(hex('092a')); }); test('it infers omitted discriminators from the variant position', () => { - // Mixed explicit and implicit discriminators: an omitted one is the - // variant's position, not the previous explicit value plus one. - const codec = getNodeCodec([ + // An omitted discriminator is the variant's position, not the previous explicit value plus one. + const codec = getNodeValueCodec([ enumTypeNode([ - enumEmptyVariantTypeNode('first', 5), - enumEmptyVariantTypeNode('second'), - enumEmptyVariantTypeNode('third', 9), + enumVariantTypeNode('first', { discriminator: 5 }), + enumVariantTypeNode('second'), + enumVariantTypeNode('third', { discriminator: 9 }), ]), ]); - expect(codec.encode({ __kind: 'First' })).toStrictEqual(hex('05')); - expect(codec.encode({ __kind: 'Second' })).toStrictEqual(hex('01')); - expect(codec.encode({ __kind: 'Third' })).toStrictEqual(hex('09')); - expect(codec.decode(hex('01'))).toStrictEqual({ __discriminator: 1, __kind: 'Second' }); - expect(codec.decode(hex('09'))).toStrictEqual({ __discriminator: 9, __kind: 'Third' }); + expect(codec.encode({ __kind: 'first' })).toStrictEqual(hex('05')); + expect(codec.encode({ __kind: 'second' })).toStrictEqual(hex('01')); + expect(codec.encode({ __kind: 'third' })).toStrictEqual(hex('09')); + expect(codec.decode(hex('01'))).toStrictEqual({ __discriminator: 1, __kind: 'second' }); + expect(codec.decode(hex('09'))).toStrictEqual({ __discriminator: 9, __kind: 'third' }); }); diff --git a/packages/dynamic-codecs/test/codecs/EventNode.test.ts b/packages/dynamic-codecs/test/codecs/EventNode.test.ts index 65b600a28..57831afb2 100644 --- a/packages/dynamic-codecs/test/codecs/EventNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/EventNode.test.ts @@ -1,21 +1,16 @@ -import { eventNode, numberTypeNode, structFieldTypeNode, structTypeNode } from '@codama/nodes'; +import { eventNode, integerTypeNode, structFieldTypeNode, structTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it delegates to the underlying event data node', () => { - const codec = getNodeCodec([ + const codec = getNodeValueCodec([ eventNode({ - data: structTypeNode([ - structFieldTypeNode({ - name: 'foo', - type: numberTypeNode('u32'), - }), - ]), - name: 'myEvent', + data: structTypeNode([structFieldTypeNode({ identifier: 'foo', type: integerTypeNode('u32') })]), + identifier: 'myEvent', }), ]); expect(codec.encode({ foo: 42 })).toStrictEqual(hex('2a000000')); - expect(codec.decode(hex('2a000000'))).toStrictEqual({ foo: 42 }); + expect(codec.decode(hex('2a000000'))).toStrictEqual({ foo: 42n }); }); diff --git a/packages/dynamic-codecs/test/codecs/FixedPointTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/FixedPointTypeNode.test.ts new file mode 100644 index 000000000..8771856d4 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/FixedPointTypeNode.test.ts @@ -0,0 +1,12 @@ +import { fixedPointTypeNode, integerTypeNode } from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../src'; +import { hex } from '../_setup'; + +test('it encodes fixed points as their raw integer', () => { + // 1.5 with a scale of 2 is stored as 150. + const codec = getNodeValueCodec([fixedPointTypeNode(integerTypeNode('u16'), 2)]); + expect(codec.encode(150)).toStrictEqual(hex('9600')); + expect(codec.decode(hex('9600'))).toBe(150n); +}); diff --git a/packages/dynamic-codecs/test/codecs/FloatTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/FloatTypeNode.test.ts new file mode 100644 index 000000000..2c9be77ba --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/FloatTypeNode.test.ts @@ -0,0 +1,23 @@ +import { floatTypeNode } from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../src'; +import { hex } from '../_setup'; + +test('f32', () => { + const codec = getNodeValueCodec([floatTypeNode('f32')]); + expect(codec.encode(1.5)).toStrictEqual(hex('0000c03f')); + expect(codec.decode(hex('0000c03f'))).toBe(1.5); +}); + +test('f64', () => { + const codec = getNodeValueCodec([floatTypeNode('f64')]); + expect(codec.encode(1.5)).toStrictEqual(hex('000000000000f83f')); + expect(codec.decode(hex('000000000000f83f'))).toBe(1.5); +}); + +test('big-endian', () => { + const codec = getNodeValueCodec([floatTypeNode('f32', { endian: 'be' })]); + expect(codec.encode(1.5)).toStrictEqual(hex('3fc00000')); + expect(codec.decode(hex('3fc00000'))).toBe(1.5); +}); diff --git a/packages/dynamic-codecs/test/codecs/HiddenPrefixTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/HiddenPrefixTypeNode.test.ts deleted file mode 100644 index 3994bfa1a..000000000 --- a/packages/dynamic-codecs/test/codecs/HiddenPrefixTypeNode.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - constantValueNode, - fixedSizeTypeNode, - hiddenPrefixTypeNode, - numberTypeNode, - numberValueNode, - stringTypeNode, -} from '@codama/nodes'; -import { expect, test } from 'vitest'; - -import { getNodeCodec } from '../../src'; -import { hex } from '../_setup'; - -test('it hides hidden prefixes from the main type', () => { - const codec = getNodeCodec([ - hiddenPrefixTypeNode(fixedSizeTypeNode(stringTypeNode('utf8'), 5), [ - constantValueNode(numberTypeNode('u64'), numberValueNode(42)), - ]), - ]); - expect(codec.encode('Alice')).toStrictEqual(hex('2a00000000000000416c696365')); - expect(codec.decode(hex('2a00000000000000416c696365'))).toStrictEqual('Alice'); -}); diff --git a/packages/dynamic-codecs/test/codecs/HiddenSuffixTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/HiddenSuffixTypeNode.test.ts deleted file mode 100644 index 865d7cbad..000000000 --- a/packages/dynamic-codecs/test/codecs/HiddenSuffixTypeNode.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - constantValueNode, - fixedSizeTypeNode, - hiddenSuffixTypeNode, - numberTypeNode, - numberValueNode, - stringTypeNode, -} from '@codama/nodes'; -import { expect, test } from 'vitest'; - -import { getNodeCodec } from '../../src'; -import { hex } from '../_setup'; - -test('it hides hidden suffixes from the main type', () => { - const codec = getNodeCodec([ - hiddenSuffixTypeNode(fixedSizeTypeNode(stringTypeNode('utf8'), 5), [ - constantValueNode(numberTypeNode('u64'), numberValueNode(42)), - ]), - ]); - expect(codec.encode('Alice')).toStrictEqual(hex('416c6963652a00000000000000')); - expect(codec.decode(hex('416c6963652a00000000000000'))).toStrictEqual('Alice'); -}); diff --git a/packages/dynamic-codecs/test/codecs/InstructionArgumentNode.test.ts b/packages/dynamic-codecs/test/codecs/InstructionArgumentNode.test.ts deleted file mode 100644 index c672e43ab..000000000 --- a/packages/dynamic-codecs/test/codecs/InstructionArgumentNode.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { instructionArgumentNode, numberTypeNode } from '@codama/nodes'; -import { expect, test } from 'vitest'; - -import { getNodeCodec } from '../../src'; -import { hex } from '../_setup'; - -test('it delegates to the type node of the argument', () => { - const codec = getNodeCodec([ - instructionArgumentNode({ - name: 'foo', - type: numberTypeNode('u32'), - }), - ]); - expect(codec.encode(42)).toStrictEqual(hex('2a000000')); - expect(codec.decode(hex('2a000000'))).toStrictEqual(42); -}); diff --git a/packages/dynamic-codecs/test/codecs/InstructionLinkNode.test.ts b/packages/dynamic-codecs/test/codecs/InstructionLinkNode.test.ts new file mode 100644 index 000000000..f9341bca7 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/InstructionLinkNode.test.ts @@ -0,0 +1,22 @@ +import { instructionLinkNode, instructionNode, integerTypeNode, programNode, rootNode } from '@codama/nodes'; +import { getRecordLinkablesVisitor, LinkableDictionary, NodeStack, visit } from '@codama/visitors-core'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodecVisitor } from '../../src'; +import { hex } from '../_setup'; + +test('it resolves the codec of instruction link nodes', () => { + // Given an instruction and a link node pointing to it. + const instruction = instructionNode({ data: integerTypeNode('u32'), identifier: 'transfer' }); + const root = rootNode(programNode({ identifier: 'myProgram', instructions: [instruction], publicKey: '1111' })); + const linkables = new LinkableDictionary(); + visit(root, getRecordLinkablesVisitor(linkables)); + + // When we get the codec of the link node from within the program. + const stack = new NodeStack([root, root.program]); + const codec = visit(instructionLinkNode('transfer'), getNodeValueCodecVisitor(linkables, { stack })); + + // Then we expect the codec of the linked instruction. + expect(codec.encode(42)).toStrictEqual(hex('2a000000')); + expect(codec.decode(hex('2a000000'))).toBe(42n); +}); diff --git a/packages/dynamic-codecs/test/codecs/InstructionNode.test.ts b/packages/dynamic-codecs/test/codecs/InstructionNode.test.ts index 1568b75d2..f5667608e 100644 --- a/packages/dynamic-codecs/test/codecs/InstructionNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/InstructionNode.test.ts @@ -1,21 +1,60 @@ -import { instructionArgumentNode, instructionNode, numberTypeNode } from '@codama/nodes'; +import { + constantValueNode, + hiddenPrefixTransformNode, + injectedValueNode, + instructionNode, + integerTypeNode, + integerValueNode, + programNode, + providedNode, + rootNode, + structFieldTypeNode, + structTypeNode, +} from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; -test('it delegates to the instruction arguments as a struct', () => { - const codec = getNodeCodec([ +test('it delegates to the instruction data', () => { + const codec = getNodeValueCodec([ instructionNode({ - arguments: [ - instructionArgumentNode({ - name: 'foo', - type: numberTypeNode('u32'), - }), - ], - name: 'myInstruction', + data: structTypeNode([structFieldTypeNode({ identifier: 'foo', type: integerTypeNode('u32') })]), + identifier: 'myInstruction', }), ]); expect(codec.encode({ foo: 42 })).toStrictEqual(hex('2a000000')); - expect(codec.decode(hex('2a000000'))).toStrictEqual({ foo: 42 }); + expect(codec.decode(hex('2a000000'))).toStrictEqual({ foo: 42n }); +}); + +test('it encodes instructions without data as empty bytes', () => { + const codec = getNodeValueCodec([instructionNode({ identifier: 'myInstruction' })]); + expect(codec.encode(undefined)).toStrictEqual(hex('')); + expect(codec.decode(hex(''))).toBeUndefined(); +}); + +test('it resolves injected values from the instruction and its ancestors', () => { + // Given a sub-instruction whose data injects values provided by itself and its parent. + const prefix = hiddenPrefixTransformNode([ + constantValueNode(integerTypeNode('u8'), injectedValueNode({ key: 'parentTag' })), + constantValueNode(integerTypeNode('u8'), injectedValueNode({ key: 'childTag' })), + ]); + const child = instructionNode({ + data: integerTypeNode('u16', { transforms: [prefix] }), + identifier: 'child', + provides: [providedNode('childTag', integerValueNode('2'))], + }); + const parent = instructionNode({ + identifier: 'parent', + provides: [providedNode('parentTag', integerValueNode('1'))], + subInstructions: [child], + }); + const root = rootNode(programNode({ identifier: 'myProgram', instructions: [parent], publicKey: '1111' })); + + // When we get the codec of the sub-instruction. + const codec = getNodeValueCodec([root, root.program, parent, child]); + + // Then both injected values are resolved. + expect(codec.encode(42)).toStrictEqual(hex('01022a00')); + expect(codec.decode(hex('01022a00'))).toBe(42n); }); diff --git a/packages/dynamic-codecs/test/codecs/IntegerTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/IntegerTypeNode.test.ts new file mode 100644 index 000000000..e77f6eaa8 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/IntegerTypeNode.test.ts @@ -0,0 +1,84 @@ +import { integerTypeNode } from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../src'; +import { hex } from '../_setup'; + +test('u8', () => { + const codec = getNodeValueCodec([integerTypeNode('u8')]); + expect(codec.encode(42)).toStrictEqual(hex('2a')); + expect(codec.encode(42n)).toStrictEqual(hex('2a')); + expect(codec.decode(hex('2a'))).toBe(42n); +}); + +test('u16', () => { + const codec = getNodeValueCodec([integerTypeNode('u16')]); + expect(codec.encode(42)).toStrictEqual(hex('2a00')); + expect(codec.decode(hex('2a00'))).toBe(42n); +}); + +test('u32', () => { + const codec = getNodeValueCodec([integerTypeNode('u32')]); + expect(codec.encode(42)).toStrictEqual(hex('2a000000')); + expect(codec.decode(hex('2a000000'))).toBe(42n); +}); + +test('u64', () => { + const codec = getNodeValueCodec([integerTypeNode('u64')]); + expect(codec.encode(42)).toStrictEqual(hex('2a00000000000000')); + expect(codec.decode(hex('2a00000000000000'))).toBe(42n); +}); + +test('u128', () => { + const codec = getNodeValueCodec([integerTypeNode('u128')]); + expect(codec.encode(42)).toStrictEqual(hex('2a000000000000000000000000000000')); + expect(codec.decode(hex('2a000000000000000000000000000000'))).toBe(42n); +}); + +test('i8', () => { + const codec = getNodeValueCodec([integerTypeNode('i8')]); + expect(codec.encode(-42)).toStrictEqual(hex('d6')); + expect(codec.decode(hex('d6'))).toBe(-42n); +}); + +test('i16', () => { + const codec = getNodeValueCodec([integerTypeNode('i16')]); + expect(codec.encode(-42)).toStrictEqual(hex('d6ff')); + expect(codec.decode(hex('d6ff'))).toBe(-42n); +}); + +test('i32', () => { + const codec = getNodeValueCodec([integerTypeNode('i32')]); + expect(codec.encode(-42)).toStrictEqual(hex('d6ffffff')); + expect(codec.decode(hex('d6ffffff'))).toBe(-42n); +}); + +test('i64', () => { + const codec = getNodeValueCodec([integerTypeNode('i64')]); + expect(codec.encode(-42)).toStrictEqual(hex('d6ffffffffffffff')); + expect(codec.decode(hex('d6ffffffffffffff'))).toBe(-42n); +}); + +test('i128', () => { + const codec = getNodeValueCodec([integerTypeNode('i128')]); + expect(codec.encode(-42)).toStrictEqual(hex('d6ffffffffffffffffffffffffffffff')); + expect(codec.decode(hex('d6ffffffffffffffffffffffffffffff'))).toBe(-42n); +}); + +test('shortU16', () => { + const codec = getNodeValueCodec([integerTypeNode('shortU16')]); + expect(codec.encode(42)).toStrictEqual(hex('2a')); + expect(codec.decode(hex('2a'))).toBe(42n); + expect(codec.encode(128)).toStrictEqual(hex('8001')); + expect(codec.decode(hex('8001'))).toBe(128n); +}); + +test('big-endian', () => { + const u16 = getNodeValueCodec([integerTypeNode('u16', { endian: 'be' })]); + expect(u16.encode(42)).toStrictEqual(hex('002a')); + expect(u16.decode(hex('002a'))).toBe(42n); + + const i64 = getNodeValueCodec([integerTypeNode('i64', { endian: 'be' })]); + expect(i64.encode(-42)).toStrictEqual(hex('ffffffffffffffd6')); + expect(i64.decode(hex('ffffffffffffffd6'))).toBe(-42n); +}); diff --git a/packages/dynamic-codecs/test/codecs/MapTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/MapTypeNode.test.ts index b61685bc4..e7a90f6f2 100644 --- a/packages/dynamic-codecs/test/codecs/MapTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/MapTypeNode.test.ts @@ -1,43 +1,38 @@ import { fixedCountNode, - fixedSizeTypeNode, + fixedSizeTransformNode, + integerTypeNode, mapTypeNode, - numberTypeNode, prefixedCountNode, remainderCountNode, stringTypeNode, } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; +const key = stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(3)] }); +const value = integerTypeNode('u16'); +// eslint-disable-next-line sort-keys +const map = { foo: 42, bar: 99, baz: 650 }; +// eslint-disable-next-line sort-keys +const decodedMap = { foo: 42n, bar: 99n, baz: 650n }; + test('it decodes prefixed maps as objects', () => { - const key = fixedSizeTypeNode(stringTypeNode('utf8'), 3); - const value = numberTypeNode('u16'); - const codec = getNodeCodec([mapTypeNode(key, value, prefixedCountNode(numberTypeNode('u32')))]); - // eslint-disable-next-line sort-keys - const map = { foo: 42, bar: 99, baz: 650 }; + const codec = getNodeValueCodec([mapTypeNode(key, value, prefixedCountNode(integerTypeNode('u32')))]); expect(codec.encode(map)).toStrictEqual(hex('03000000666f6f2a00626172630062617a8a02')); - expect(codec.decode(hex('03000000666f6f2a00626172630062617a8a02'))).toStrictEqual(map); + expect(codec.decode(hex('03000000666f6f2a00626172630062617a8a02'))).toStrictEqual(decodedMap); }); test('it decodes fixed maps as objects', () => { - const key = fixedSizeTypeNode(stringTypeNode('utf8'), 3); - const value = numberTypeNode('u16'); - const codec = getNodeCodec([mapTypeNode(key, value, fixedCountNode(3))]); - // eslint-disable-next-line sort-keys - const map = { foo: 42, bar: 99, baz: 650 }; + const codec = getNodeValueCodec([mapTypeNode(key, value, fixedCountNode(3))]); expect(codec.encode(map)).toStrictEqual(hex('666f6f2a00626172630062617a8a02')); - expect(codec.decode(hex('666f6f2a00626172630062617a8a02'))).toStrictEqual(map); + expect(codec.decode(hex('666f6f2a00626172630062617a8a02'))).toStrictEqual(decodedMap); }); test('it decodes remainder maps as objects', () => { - const key = fixedSizeTypeNode(stringTypeNode('utf8'), 3); - const value = numberTypeNode('u16'); - const codec = getNodeCodec([mapTypeNode(key, value, remainderCountNode())]); - // eslint-disable-next-line sort-keys - const map = { foo: 42, bar: 99, baz: 650 }; + const codec = getNodeValueCodec([mapTypeNode(key, value, remainderCountNode())]); expect(codec.encode(map)).toStrictEqual(hex('666f6f2a00626172630062617a8a02')); - expect(codec.decode(hex('666f6f2a00626172630062617a8a02'))).toStrictEqual(map); + expect(codec.decode(hex('666f6f2a00626172630062617a8a02'))).toStrictEqual(decodedMap); }); diff --git a/packages/dynamic-codecs/test/codecs/NumberTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/NumberTypeNode.test.ts deleted file mode 100644 index b59d2bc48..000000000 --- a/packages/dynamic-codecs/test/codecs/NumberTypeNode.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { numberTypeNode } from '@codama/nodes'; -import { expect, test } from 'vitest'; - -import { getNodeCodec } from '../../src'; -import { hex } from '../_setup'; - -test('u8', () => { - const codec = getNodeCodec([numberTypeNode('u8')]); - expect(codec.encode(42)).toStrictEqual(hex('2a')); - expect(codec.decode(hex('2a'))).toBe(42); -}); - -test('u16', () => { - const codec = getNodeCodec([numberTypeNode('u16')]); - expect(codec.encode(42)).toStrictEqual(hex('2a00')); - expect(codec.decode(hex('2a00'))).toBe(42); -}); - -test('u32', () => { - const codec = getNodeCodec([numberTypeNode('u32')]); - expect(codec.encode(42)).toStrictEqual(hex('2a000000')); - expect(codec.decode(hex('2a000000'))).toBe(42); -}); - -test('u64', () => { - const codec = getNodeCodec([numberTypeNode('u64')]); - expect(codec.encode(42)).toStrictEqual(hex('2a00000000000000')); - expect(codec.decode(hex('2a00000000000000'))).toBe(42n); -}); - -test('u128', () => { - const codec = getNodeCodec([numberTypeNode('u128')]); - expect(codec.encode(42)).toStrictEqual(hex('2a000000000000000000000000000000')); - expect(codec.decode(hex('2a000000000000000000000000000000'))).toBe(42n); -}); - -test('i8', () => { - const codec = getNodeCodec([numberTypeNode('i8')]); - expect(codec.encode(-42)).toStrictEqual(hex('d6')); - expect(codec.decode(hex('d6'))).toBe(-42); -}); - -test('i16', () => { - const codec = getNodeCodec([numberTypeNode('i16')]); - expect(codec.encode(-42)).toStrictEqual(hex('d6ff')); - expect(codec.decode(hex('d6ff'))).toBe(-42); -}); - -test('i32', () => { - const codec = getNodeCodec([numberTypeNode('i32')]); - expect(codec.encode(-42)).toStrictEqual(hex('d6ffffff')); - expect(codec.decode(hex('d6ffffff'))).toBe(-42); -}); - -test('i64', () => { - const codec = getNodeCodec([numberTypeNode('i64')]); - expect(codec.encode(-42)).toStrictEqual(hex('d6ffffffffffffff')); - expect(codec.decode(hex('d6ffffffffffffff'))).toBe(-42n); -}); - -test('i128', () => { - const codec = getNodeCodec([numberTypeNode('i128')]); - expect(codec.encode(-42)).toStrictEqual(hex('d6ffffffffffffffffffffffffffffff')); - expect(codec.decode(hex('d6ffffffffffffffffffffffffffffff'))).toBe(-42n); -}); - -test('f32', () => { - const codec = getNodeCodec([numberTypeNode('f32')]); - expect(codec.encode(1.5)).toStrictEqual(hex('0000c03f')); - expect(codec.decode(hex('0000c03f'))).toBe(1.5); -}); - -test('f64', () => { - const codec = getNodeCodec([numberTypeNode('f64')]); - expect(codec.encode(1.5)).toStrictEqual(hex('000000000000f83f')); - expect(codec.decode(hex('000000000000f83f'))).toBe(1.5); -}); - -test('shortU16', () => { - const codec = getNodeCodec([numberTypeNode('shortU16')]); - expect(codec.encode(42)).toStrictEqual(hex('2a')); - expect(codec.decode(hex('2a'))).toBe(42); - expect(codec.encode(128)).toStrictEqual(hex('8001')); - expect(codec.decode(hex('8001'))).toBe(128); -}); diff --git a/packages/dynamic-codecs/test/codecs/OptionTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/OptionTypeNode.test.ts index 8ea52c7c2..c4a117cd2 100644 --- a/packages/dynamic-codecs/test/codecs/OptionTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/OptionTypeNode.test.ts @@ -1,29 +1,29 @@ -import { numberTypeNode, optionTypeNode } from '@codama/nodes'; +import { integerTypeNode, optionTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it encodes prefixed options', () => { - const codec = getNodeCodec([optionTypeNode(numberTypeNode('u16'))]); + const codec = getNodeValueCodec([optionTypeNode(integerTypeNode('u16'))]); expect(codec.encode({ __option: 'Some', value: 42 })).toStrictEqual(hex('012a00')); - expect(codec.decode(hex('012a00'))).toStrictEqual({ __option: 'Some', value: 42 }); + expect(codec.decode(hex('012a00'))).toStrictEqual({ __option: 'Some', value: 42n }); expect(codec.encode({ __option: 'None' })).toStrictEqual(hex('00')); expect(codec.decode(hex('00'))).toStrictEqual({ __option: 'None' }); }); test('it encodes prefixed options with custom sizes', () => { - const codec = getNodeCodec([optionTypeNode(numberTypeNode('u16'), { prefix: numberTypeNode('u32') })]); + const codec = getNodeValueCodec([optionTypeNode(integerTypeNode('u16'), { prefix: integerTypeNode('u32') })]); expect(codec.encode({ __option: 'Some', value: 42 })).toStrictEqual(hex('010000002a00')); - expect(codec.decode(hex('010000002a00'))).toStrictEqual({ __option: 'Some', value: 42 }); + expect(codec.decode(hex('010000002a00'))).toStrictEqual({ __option: 'Some', value: 42n }); expect(codec.encode({ __option: 'None' })).toStrictEqual(hex('00000000')); expect(codec.decode(hex('00000000'))).toStrictEqual({ __option: 'None' }); }); test('it encodes prefixed options with fixed size items', () => { - const codec = getNodeCodec([optionTypeNode(numberTypeNode('u16'), { fixed: true })]); + const codec = getNodeValueCodec([optionTypeNode(integerTypeNode('u16'), { fixed: true })]); expect(codec.encode({ __option: 'Some', value: 42 })).toStrictEqual(hex('012a00')); - expect(codec.decode(hex('012a00'))).toStrictEqual({ __option: 'Some', value: 42 }); + expect(codec.decode(hex('012a00'))).toStrictEqual({ __option: 'Some', value: 42n }); expect(codec.encode({ __option: 'None' })).toStrictEqual(hex('000000')); expect(codec.decode(hex('000000'))).toStrictEqual({ __option: 'None' }); }); diff --git a/packages/dynamic-codecs/test/codecs/PostOffsetTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/PostOffsetTypeNode.test.ts deleted file mode 100644 index 770473407..000000000 --- a/packages/dynamic-codecs/test/codecs/PostOffsetTypeNode.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { fixedSizeTypeNode, numberTypeNode, postOffsetTypeNode, preOffsetTypeNode, tupleTypeNode } from '@codama/nodes'; -import { expect, test } from 'vitest'; - -import { getNodeCodec } from '../../src'; -import { hex } from '../_setup'; - -test('it encodes relative post-offsets', () => { - const node = tupleTypeNode([ - postOffsetTypeNode(fixedSizeTypeNode(numberTypeNode('u8'), 4), -2), - numberTypeNode('u8'), - ]); - const codec = getNodeCodec([node]); - expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('aa00ff0000')); - expect(codec.decode(hex('aa00ff0000'))).toStrictEqual([0xaa, 0xff]); -}); - -test('it encodes padded post-offsets', () => { - const node = tupleTypeNode([postOffsetTypeNode(numberTypeNode('u8'), 4, 'padded'), numberTypeNode('u8')]); - const codec = getNodeCodec([node]); - expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('aa00000000ff')); - expect(codec.decode(hex('aa00000000ff'))).toStrictEqual([0xaa, 0xff]); -}); - -test('it encodes absolute post-offsets', () => { - const node = tupleTypeNode([ - postOffsetTypeNode(fixedSizeTypeNode(numberTypeNode('u8'), 4), -2, 'absolute'), - numberTypeNode('u8'), - ]); - const codec = getNodeCodec([node]); - expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('aa0000ff00')); - expect(codec.decode(hex('aa0000ff00'))).toStrictEqual([0xaa, 0xff]); -}); - -test('it encodes post-offsets relative to the previous pre-offset', () => { - const node = tupleTypeNode([ - postOffsetTypeNode(preOffsetTypeNode(numberTypeNode('u8'), 4, 'padded'), 0, 'preOffset'), - numberTypeNode('u8'), - ]); - const codec = getNodeCodec([node]); - expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('ff000000aa00')); - expect(codec.decode(hex('ff000000aa00'))).toStrictEqual([0xaa, 0xff]); -}); diff --git a/packages/dynamic-codecs/test/codecs/PreOffsetTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/PreOffsetTypeNode.test.ts deleted file mode 100644 index 10c710697..000000000 --- a/packages/dynamic-codecs/test/codecs/PreOffsetTypeNode.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { numberTypeNode, preOffsetTypeNode, tupleTypeNode } from '@codama/nodes'; -import { expect, test } from 'vitest'; - -import { getNodeCodec } from '../../src'; -import { hex } from '../_setup'; - -test('it encodes relative pre-offsets', () => { - const node = tupleTypeNode([ - preOffsetTypeNode(numberTypeNode('u8'), 1), - preOffsetTypeNode(numberTypeNode('u8'), -2), - ]); - const codec = getNodeCodec([node]); - expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('ffaa')); - expect(codec.decode(hex('ffaa'))).toStrictEqual([0xaa, 0xff]); -}); - -test('it encodes padded pre-offsets', () => { - const node = tupleTypeNode([preOffsetTypeNode(numberTypeNode('u8'), 4, 'padded'), numberTypeNode('u8')]); - const codec = getNodeCodec([node]); - expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('00000000aaff')); - expect(codec.decode(hex('00000000aaff'))).toStrictEqual([0xaa, 0xff]); -}); - -test('it encodes absolute pre-offsets', () => { - const node = tupleTypeNode([ - preOffsetTypeNode(numberTypeNode('u8'), 1), - preOffsetTypeNode(numberTypeNode('u8'), 0, 'absolute'), - ]); - const codec = getNodeCodec([node]); - expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('ffaa')); - expect(codec.decode(hex('ffaa'))).toStrictEqual([0xaa, 0xff]); -}); diff --git a/packages/dynamic-codecs/test/codecs/PublicKeyTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/PublicKeyTypeNode.test.ts index 8655185be..c162e5b2f 100644 --- a/packages/dynamic-codecs/test/codecs/PublicKeyTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/PublicKeyTypeNode.test.ts @@ -1,11 +1,11 @@ import { publicKeyTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it decodes as a base58 string', () => { - const codec = getNodeCodec([publicKeyTypeNode()]); + const codec = getNodeValueCodec([publicKeyTypeNode()]); expect(codec.encode('LorisCg1FTs89a32VSrFskYDgiRbNQzct1WxyZb7nuA')).toStrictEqual( hex('0513045e052f4919b608963de73c666e0672e06e28140ab841bff1cc83a178b5'), ); diff --git a/packages/dynamic-codecs/test/codecs/RemainderOptionTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/RemainderOptionTypeNode.test.ts index 8fdbcb9c8..d3feddcd2 100644 --- a/packages/dynamic-codecs/test/codecs/RemainderOptionTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/RemainderOptionTypeNode.test.ts @@ -1,13 +1,13 @@ -import { numberTypeNode, remainderOptionTypeNode } from '@codama/nodes'; +import { integerTypeNode, remainderOptionTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it encodes remainder options', () => { - const codec = getNodeCodec([remainderOptionTypeNode(numberTypeNode('u16'))]); + const codec = getNodeValueCodec([remainderOptionTypeNode(integerTypeNode('u16'))]); expect(codec.encode({ __option: 'Some', value: 42 })).toStrictEqual(hex('2a00')); - expect(codec.decode(hex('2a00'))).toStrictEqual({ __option: 'Some', value: 42 }); + expect(codec.decode(hex('2a00'))).toStrictEqual({ __option: 'Some', value: 42n }); expect(codec.encode({ __option: 'None' })).toStrictEqual(hex('')); expect(codec.decode(hex(''))).toStrictEqual({ __option: 'None' }); }); diff --git a/packages/dynamic-codecs/test/codecs/SentinelCountNode.test.ts b/packages/dynamic-codecs/test/codecs/SentinelCountNode.test.ts new file mode 100644 index 000000000..b2be73354 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/SentinelCountNode.test.ts @@ -0,0 +1,83 @@ +import { + arrayTypeNode, + constantValueNodeFromBytes, + fixedSizeTransformNode, + integerTypeNode, + mapTypeNode, + sentinelCountNode, + setTypeNode, + stringTypeNode, + tupleTypeNode, +} from '@codama/nodes'; +import { SOLANA_ERROR__CODECS__SENTINEL_MISSING_AT_END_OF_BYTES, SolanaError } from '@solana/errors'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../src'; +import { hex } from '../_setup'; + +const sentinel = constantValueNodeFromBytes('base16', 'ffff'); + +test('it reads items until the sentinel is found', () => { + const codec = getNodeValueCodec([arrayTypeNode(integerTypeNode('u16'), sentinelCountNode(sentinel))]); + expect(codec.encode([42, 99])).toStrictEqual(hex('2a006300ffff')); + expect(codec.decode(hex('2a006300ffff'))).toStrictEqual([42n, 99n]); + expect(codec.encode([])).toStrictEqual(hex('ffff')); + expect(codec.decode(hex('ffff'))).toStrictEqual([]); +}); + +test('it only compares the sentinel at item boundaries', () => { + // The first item contains the sentinel bytes at an offset of one byte. + const item = tupleTypeNode([integerTypeNode('u8'), integerTypeNode('u16')]); + const codec = getNodeValueCodec([arrayTypeNode(item, sentinelCountNode(sentinel))]); + expect(codec.encode([[1, 0xffff]])).toStrictEqual(hex('01ffffffff')); + expect(codec.decode(hex('01ffffffff'))).toStrictEqual([[1n, 0xffffn]]); +}); + +test('it lets the next attribute read the bytes after the sentinel', () => { + const codec = getNodeValueCodec([ + tupleTypeNode([arrayTypeNode(integerTypeNode('u8'), sentinelCountNode(sentinel)), integerTypeNode('u8')]), + ]); + expect(codec.encode([[1, 2], 3])).toStrictEqual(hex('0102ffff03')); + expect(codec.decode(hex('0102ffff03'))).toStrictEqual([[1n, 2n], 3n]); +}); + +test('it requires the sentinel by default', () => { + const codec = getNodeValueCodec([arrayTypeNode(integerTypeNode('u16'), sentinelCountNode(sentinel))]); + expect(() => codec.decode(hex('2a006300'))).toThrow( + new SolanaError(SOLANA_ERROR__CODECS__SENTINEL_MISSING_AT_END_OF_BYTES, { + codecDescription: 'array', + hexSentinel: 'ffff', + sentinel: hex('ffff'), + }), + ); +}); + +test('it writes the sentinel but tolerates its absence with the optional strategy', () => { + const codec = getNodeValueCodec([ + arrayTypeNode(integerTypeNode('u16'), sentinelCountNode(sentinel, { strategy: 'optional' })), + ]); + expect(codec.encode([42, 99])).toStrictEqual(hex('2a006300ffff')); + expect(codec.decode(hex('2a006300ffff'))).toStrictEqual([42n, 99n]); + expect(codec.decode(hex('2a006300'))).toStrictEqual([42n, 99n]); +}); + +test('it never writes the sentinel with the omitted strategy', () => { + const codec = getNodeValueCodec([ + arrayTypeNode(integerTypeNode('u16'), sentinelCountNode(sentinel, { strategy: 'omitted' })), + ]); + expect(codec.encode([42, 99])).toStrictEqual(hex('2a006300')); + expect(codec.decode(hex('2a006300'))).toStrictEqual([42n, 99n]); + // A sentinel that is present is still consumed. + expect(codec.decode(hex('2a006300ffff'))).toStrictEqual([42n, 99n]); +}); + +test('it supports sets and maps', () => { + const set = getNodeValueCodec([setTypeNode(integerTypeNode('u8'), sentinelCountNode(sentinel))]); + expect(set.encode([1, 2])).toStrictEqual(hex('0102ffff')); + expect(set.decode(hex('0102ffff'))).toStrictEqual([1n, 2n]); + + const key = stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(1)] }); + const map = getNodeValueCodec([mapTypeNode(key, integerTypeNode('u8'), sentinelCountNode(sentinel))]); + expect(map.encode({ a: 1 })).toStrictEqual(hex('6101ffff')); + expect(map.decode(hex('6101ffff'))).toStrictEqual({ a: 1n }); +}); diff --git a/packages/dynamic-codecs/test/codecs/SentinelTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/SentinelTypeNode.test.ts deleted file mode 100644 index 3dcfd940f..000000000 --- a/packages/dynamic-codecs/test/codecs/SentinelTypeNode.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { constantValueNodeFromBytes, sentinelTypeNode, stringTypeNode } from '@codama/nodes'; -import { expect, test } from 'vitest'; - -import { getNodeCodec } from '../../src'; -import { hex } from '../_setup'; - -test('it encodes sentinel types', () => { - const sentinel = constantValueNodeFromBytes('base16', 'ffff'); - const codec = getNodeCodec([sentinelTypeNode(stringTypeNode('utf8'), sentinel)]); - expect(codec.encode('Hello World!')).toStrictEqual(hex('48656c6c6f20576f726c6421ffff')); - expect(codec.decode(hex('48656c6c6f20576f726c6421ffff'))).toBe('Hello World!'); -}); diff --git a/packages/dynamic-codecs/test/codecs/SetTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/SetTypeNode.test.ts index c60d6f176..ced2dff36 100644 --- a/packages/dynamic-codecs/test/codecs/SetTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/SetTypeNode.test.ts @@ -1,23 +1,23 @@ -import { fixedCountNode, numberTypeNode, prefixedCountNode, remainderCountNode, setTypeNode } from '@codama/nodes'; +import { fixedCountNode, integerTypeNode, prefixedCountNode, remainderCountNode, setTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it decodes prefixed sets', () => { - const codec = getNodeCodec([setTypeNode(numberTypeNode('u16'), prefixedCountNode(numberTypeNode('u32')))]); + const codec = getNodeValueCodec([setTypeNode(integerTypeNode('u16'), prefixedCountNode(integerTypeNode('u32')))]); expect(codec.encode([42, 99, 650])).toStrictEqual(hex('030000002a0063008a02')); - expect(codec.decode(hex('030000002a0063008a02'))).toStrictEqual([42, 99, 650]); + expect(codec.decode(hex('030000002a0063008a02'))).toStrictEqual([42n, 99n, 650n]); }); test('it decodes fixed sets', () => { - const codec = getNodeCodec([setTypeNode(numberTypeNode('u16'), fixedCountNode(3))]); + const codec = getNodeValueCodec([setTypeNode(integerTypeNode('u16'), fixedCountNode(3))]); expect(codec.encode([42, 99, 650])).toStrictEqual(hex('2a0063008a02')); - expect(codec.decode(hex('2a0063008a02'))).toStrictEqual([42, 99, 650]); + expect(codec.decode(hex('2a0063008a02'))).toStrictEqual([42n, 99n, 650n]); }); test('it decodes remainder sets', () => { - const codec = getNodeCodec([setTypeNode(numberTypeNode('u16'), remainderCountNode())]); + const codec = getNodeValueCodec([setTypeNode(integerTypeNode('u16'), remainderCountNode())]); expect(codec.encode([42, 99, 650])).toStrictEqual(hex('2a0063008a02')); - expect(codec.decode(hex('2a0063008a02'))).toStrictEqual([42, 99, 650]); + expect(codec.decode(hex('2a0063008a02'))).toStrictEqual([42n, 99n, 650n]); }); diff --git a/packages/dynamic-codecs/test/codecs/SizePrefixTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/SizePrefixTypeNode.test.ts deleted file mode 100644 index 37860749a..000000000 --- a/packages/dynamic-codecs/test/codecs/SizePrefixTypeNode.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { numberTypeNode, sizePrefixTypeNode, stringTypeNode } from '@codama/nodes'; -import { expect, test } from 'vitest'; - -import { getNodeCodec } from '../../src'; -import { hex } from '../_setup'; - -test('it encodes types prefixed with their sizes', () => { - const codec = getNodeCodec([sizePrefixTypeNode(stringTypeNode('utf8'), numberTypeNode('u32'))]); - expect(codec.encode('Hello World!')).toStrictEqual(hex('0c00000048656c6c6f20576f726c6421')); - expect(codec.decode(hex('0c00000048656c6c6f20576f726c6421'))).toBe('Hello World!'); -}); diff --git a/packages/dynamic-codecs/test/codecs/StringTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/StringTypeNode.test.ts index af05b6c08..794533b2f 100644 --- a/packages/dynamic-codecs/test/codecs/StringTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/StringTypeNode.test.ts @@ -1,29 +1,29 @@ import { stringTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('base16', () => { - const codec = getNodeCodec([stringTypeNode('base16')]); + const codec = getNodeValueCodec([stringTypeNode('base16')]); expect(codec.encode('deadb0d1e5')).toStrictEqual(hex('deadb0d1e5')); expect(codec.decode(hex('deadb0d1e5'))).toBe('deadb0d1e5'); }); test('base58', () => { - const codec = getNodeCodec([stringTypeNode('base58')]); + const codec = getNodeValueCodec([stringTypeNode('base58')]); expect(codec.encode('heLLo')).toStrictEqual(hex('1b6a3070')); expect(codec.decode(hex('1b6a3070'))).toBe('heLLo'); }); test('base64', () => { - const codec = getNodeCodec([stringTypeNode('base64')]); + const codec = getNodeValueCodec([stringTypeNode('base64')]); expect(codec.encode('HelloWorld++')).toStrictEqual(hex('1de965a16a2b95dfbe')); expect(codec.decode(hex('1de965a16a2b95dfbe'))).toBe('HelloWorld++'); }); test('utf8', () => { - const codec = getNodeCodec([stringTypeNode('utf8')]); + const codec = getNodeValueCodec([stringTypeNode('utf8')]); expect(codec.encode('Hello World!')).toStrictEqual(hex('48656c6c6f20576f726c6421')); expect(codec.decode(hex('48656c6c6f20576f726c6421'))).toBe('Hello World!'); }); diff --git a/packages/dynamic-codecs/test/codecs/StructFieldTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/StructFieldTypeNode.test.ts index c998c8e3e..9bce32feb 100644 --- a/packages/dynamic-codecs/test/codecs/StructFieldTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/StructFieldTypeNode.test.ts @@ -1,11 +1,11 @@ -import { numberTypeNode, structFieldTypeNode } from '@codama/nodes'; +import { integerTypeNode, structFieldTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it encodes struct fields using their types', () => { - const codec = getNodeCodec([structFieldTypeNode({ name: 'age', type: numberTypeNode('u16') })]); + const codec = getNodeValueCodec([structFieldTypeNode({ identifier: 'age', type: integerTypeNode('u16') })]); expect(codec.encode(42)).toStrictEqual(hex('2a00')); - expect(codec.decode(hex('2a00'))).toStrictEqual(42); + expect(codec.decode(hex('2a00'))).toBe(42n); }); diff --git a/packages/dynamic-codecs/test/codecs/StructTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/StructTypeNode.test.ts index e806c584a..d2903c41f 100644 --- a/packages/dynamic-codecs/test/codecs/StructTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/StructTypeNode.test.ts @@ -1,17 +1,33 @@ -import { fixedSizeTypeNode, numberTypeNode, stringTypeNode, structFieldTypeNode, structTypeNode } from '@codama/nodes'; +import { + fixedSizeTransformNode, + integerTypeNode, + stringTypeNode, + structFieldTypeNode, + structTypeNode, +} from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it encodes structs', () => { - const codec = getNodeCodec([ + const codec = getNodeValueCodec([ structTypeNode([ - structFieldTypeNode({ name: 'firstname', type: fixedSizeTypeNode(stringTypeNode('utf8'), 5) }), - structFieldTypeNode({ name: 'age', type: numberTypeNode('u16') }), + structFieldTypeNode({ + identifier: 'firstname', + type: stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(5)] }), + }), + structFieldTypeNode({ identifier: 'age', type: integerTypeNode('u16') }), ]), ]); - const person = { age: 42, firstname: 'Alice' }; - expect(codec.encode(person)).toStrictEqual(hex('416c6963652a00')); - expect(codec.decode(hex('416c6963652a00'))).toStrictEqual(person); + expect(codec.encode({ age: 42, firstname: 'Alice' })).toStrictEqual(hex('416c6963652a00')); + expect(codec.decode(hex('416c6963652a00'))).toStrictEqual({ age: 42n, firstname: 'Alice' }); +}); + +test('it uses the raw field identifiers', () => { + const codec = getNodeValueCodec([ + structTypeNode([structFieldTypeNode({ identifier: 'my_field', type: integerTypeNode('u8') })]), + ]); + expect(codec.encode({ my_field: 42 })).toStrictEqual(hex('2a')); + expect(codec.decode(hex('2a'))).toStrictEqual({ my_field: 42n }); }); diff --git a/packages/dynamic-codecs/test/codecs/TupleTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/TupleTypeNode.test.ts index a69575460..23deb44a7 100644 --- a/packages/dynamic-codecs/test/codecs/TupleTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/TupleTypeNode.test.ts @@ -1,11 +1,13 @@ -import { fixedSizeTypeNode, numberTypeNode, stringTypeNode, tupleTypeNode } from '@codama/nodes'; +import { fixedSizeTransformNode, integerTypeNode, stringTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it encodes tuples', () => { - const codec = getNodeCodec([tupleTypeNode([fixedSizeTypeNode(stringTypeNode('utf8'), 3), numberTypeNode('u16')])]); + const codec = getNodeValueCodec([ + tupleTypeNode([stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(3)] }), integerTypeNode('u16')]), + ]); expect(codec.encode(['foo', 42])).toStrictEqual(hex('666f6f2a00')); - expect(codec.decode(hex('666f6f2a00'))).toStrictEqual(['foo', 42]); + expect(codec.decode(hex('666f6f2a00'))).toStrictEqual(['foo', 42n]); }); diff --git a/packages/dynamic-codecs/test/codecs/ZeroableOptionTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/ZeroableOptionTypeNode.test.ts index 40538bd98..d67453b07 100644 --- a/packages/dynamic-codecs/test/codecs/ZeroableOptionTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/ZeroableOptionTypeNode.test.ts @@ -1,22 +1,22 @@ -import { constantValueNodeFromBytes, numberTypeNode, zeroableOptionTypeNode } from '@codama/nodes'; +import { constantValueNodeFromBytes, integerTypeNode, zeroableOptionTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; +import { getNodeValueCodec } from '../../src'; import { hex } from '../_setup'; test('it encodes zeroable options', () => { - const codec = getNodeCodec([zeroableOptionTypeNode(numberTypeNode('u16'))]); + const codec = getNodeValueCodec([zeroableOptionTypeNode(integerTypeNode('u16'))]); expect(codec.encode({ __option: 'Some', value: 42 })).toStrictEqual(hex('2a00')); - expect(codec.decode(hex('2a00'))).toStrictEqual({ __option: 'Some', value: 42 }); + expect(codec.decode(hex('2a00'))).toStrictEqual({ __option: 'Some', value: 42n }); expect(codec.encode({ __option: 'None' })).toStrictEqual(hex('0000')); expect(codec.decode(hex('0000'))).toStrictEqual({ __option: 'None' }); }); test('it encodes zeroable options with custom zero values', () => { const zeroValue = constantValueNodeFromBytes('base16', 'ffff'); - const codec = getNodeCodec([zeroableOptionTypeNode(numberTypeNode('u16'), zeroValue)]); + const codec = getNodeValueCodec([zeroableOptionTypeNode(integerTypeNode('u16'), { zeroValue })]); expect(codec.encode({ __option: 'Some', value: 42 })).toStrictEqual(hex('2a00')); - expect(codec.decode(hex('2a00'))).toStrictEqual({ __option: 'Some', value: 42 }); + expect(codec.decode(hex('2a00'))).toStrictEqual({ __option: 'Some', value: 42n }); expect(codec.encode({ __option: 'None' })).toStrictEqual(hex('ffff')); expect(codec.decode(hex('ffff'))).toStrictEqual({ __option: 'None' }); }); diff --git a/packages/dynamic-codecs/test/codecs/FixedSizeTypeNode.test.ts b/packages/dynamic-codecs/test/codecs/transforms/FixedSizeTransformNode.test.ts similarity index 56% rename from packages/dynamic-codecs/test/codecs/FixedSizeTypeNode.test.ts rename to packages/dynamic-codecs/test/codecs/transforms/FixedSizeTransformNode.test.ts index 7cd15fd83..df5d0044f 100644 --- a/packages/dynamic-codecs/test/codecs/FixedSizeTypeNode.test.ts +++ b/packages/dynamic-codecs/test/codecs/transforms/FixedSizeTransformNode.test.ts @@ -1,11 +1,11 @@ -import { fixedSizeTypeNode, stringTypeNode } from '@codama/nodes'; +import { fixedSizeTransformNode, stringTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getNodeCodec } from '../../src'; -import { hex } from '../_setup'; +import { getNodeValueCodec } from '../../../src'; +import { hex } from '../../_setup'; test('it decodes fixed size strings', () => { - const codec = getNodeCodec([fixedSizeTypeNode(stringTypeNode('utf8'), 5)]); + const codec = getNodeValueCodec([stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(5)] })]); expect(codec.encode('Hello')).toStrictEqual(hex('48656c6c6f')); expect(codec.decode(hex('48656c6c6f'))).toBe('Hello'); expect(codec.encode('Sup')).toStrictEqual(hex('5375700000')); diff --git a/packages/dynamic-codecs/test/codecs/transforms/HiddenPrefixTransformNode.test.ts b/packages/dynamic-codecs/test/codecs/transforms/HiddenPrefixTransformNode.test.ts new file mode 100644 index 000000000..5ad5a4cc1 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/transforms/HiddenPrefixTransformNode.test.ts @@ -0,0 +1,19 @@ +import { + constantValueNode, + fixedSizeTransformNode, + hiddenPrefixTransformNode, + integerTypeNode, + integerValueNode, + stringTypeNode, +} from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../../src'; +import { hex } from '../../_setup'; + +test('it hides hidden prefixes from the main type', () => { + const prefix = hiddenPrefixTransformNode([constantValueNode(integerTypeNode('u64'), integerValueNode('42'))]); + const codec = getNodeValueCodec([stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(5), prefix] })]); + expect(codec.encode('Alice')).toStrictEqual(hex('2a00000000000000416c696365')); + expect(codec.decode(hex('2a00000000000000416c696365'))).toBe('Alice'); +}); diff --git a/packages/dynamic-codecs/test/codecs/transforms/HiddenSuffixTransformNode.test.ts b/packages/dynamic-codecs/test/codecs/transforms/HiddenSuffixTransformNode.test.ts new file mode 100644 index 000000000..ce265f1f1 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/transforms/HiddenSuffixTransformNode.test.ts @@ -0,0 +1,19 @@ +import { + constantValueNode, + fixedSizeTransformNode, + hiddenSuffixTransformNode, + integerTypeNode, + integerValueNode, + stringTypeNode, +} from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../../src'; +import { hex } from '../../_setup'; + +test('it hides hidden suffixes from the main type', () => { + const suffix = hiddenSuffixTransformNode([constantValueNode(integerTypeNode('u64'), integerValueNode('42'))]); + const codec = getNodeValueCodec([stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(5), suffix] })]); + expect(codec.encode('Alice')).toStrictEqual(hex('416c6963652a00000000000000')); + expect(codec.decode(hex('416c6963652a00000000000000'))).toBe('Alice'); +}); diff --git a/packages/dynamic-codecs/test/codecs/transforms/PostOffsetTransformNode.test.ts b/packages/dynamic-codecs/test/codecs/transforms/PostOffsetTransformNode.test.ts new file mode 100644 index 000000000..4b925951e --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/transforms/PostOffsetTransformNode.test.ts @@ -0,0 +1,58 @@ +import { + fixedSizeTransformNode, + integerTypeNode, + postOffsetTransformNode, + preOffsetTransformNode, + tupleTypeNode, +} from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../../src'; +import { hex } from '../../_setup'; + +test('it encodes relative post-offsets', () => { + const node = tupleTypeNode([ + integerTypeNode('u8', { transforms: [fixedSizeTransformNode(4), postOffsetTransformNode(-2)] }), + integerTypeNode('u8'), + ]); + const codec = getNodeValueCodec([node]); + expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('aa00ff0000')); + expect(codec.decode(hex('aa00ff0000'))).toStrictEqual([0xaan, 0xffn]); +}); + +test('it encodes padded post-offsets', () => { + const node = tupleTypeNode([ + integerTypeNode('u8', { transforms: [postOffsetTransformNode(4, { strategy: 'padded' })] }), + integerTypeNode('u8'), + ]); + const codec = getNodeValueCodec([node]); + expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('aa00000000ff')); + expect(codec.decode(hex('aa00000000ff'))).toStrictEqual([0xaan, 0xffn]); +}); + +test('it encodes absolute post-offsets', () => { + const node = tupleTypeNode([ + integerTypeNode('u8', { + transforms: [fixedSizeTransformNode(4), postOffsetTransformNode(-2, { strategy: 'absolute' })], + }), + integerTypeNode('u8'), + ]); + const codec = getNodeValueCodec([node]); + expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('aa0000ff00')); + expect(codec.decode(hex('aa0000ff00'))).toStrictEqual([0xaan, 0xffn]); +}); + +test('it encodes post-offsets relative to the previous pre-offset', () => { + const node = tupleTypeNode([ + integerTypeNode('u8', { + transforms: [ + preOffsetTransformNode(4, { strategy: 'padded' }), + postOffsetTransformNode(0, { strategy: 'preOffset' }), + ], + }), + integerTypeNode('u8'), + ]); + const codec = getNodeValueCodec([node]); + expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('ff000000aa00')); + expect(codec.decode(hex('ff000000aa00'))).toStrictEqual([0xaan, 0xffn]); +}); diff --git a/packages/dynamic-codecs/test/codecs/transforms/PreOffsetTransformNode.test.ts b/packages/dynamic-codecs/test/codecs/transforms/PreOffsetTransformNode.test.ts new file mode 100644 index 000000000..6f056e431 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/transforms/PreOffsetTransformNode.test.ts @@ -0,0 +1,35 @@ +import { integerTypeNode, preOffsetTransformNode, tupleTypeNode } from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../../src'; +import { hex } from '../../_setup'; + +test('it encodes relative pre-offsets', () => { + const node = tupleTypeNode([ + integerTypeNode('u8', { transforms: [preOffsetTransformNode(1)] }), + integerTypeNode('u8', { transforms: [preOffsetTransformNode(-2)] }), + ]); + const codec = getNodeValueCodec([node]); + expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('ffaa')); + expect(codec.decode(hex('ffaa'))).toStrictEqual([0xaan, 0xffn]); +}); + +test('it encodes padded pre-offsets', () => { + const node = tupleTypeNode([ + integerTypeNode('u8', { transforms: [preOffsetTransformNode(4, { strategy: 'padded' })] }), + integerTypeNode('u8'), + ]); + const codec = getNodeValueCodec([node]); + expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('00000000aaff')); + expect(codec.decode(hex('00000000aaff'))).toStrictEqual([0xaan, 0xffn]); +}); + +test('it encodes absolute pre-offsets', () => { + const node = tupleTypeNode([ + integerTypeNode('u8', { transforms: [preOffsetTransformNode(1)] }), + integerTypeNode('u8', { transforms: [preOffsetTransformNode(0, { strategy: 'absolute' })] }), + ]); + const codec = getNodeValueCodec([node]); + expect(codec.encode([0xaa, 0xff])).toStrictEqual(hex('ffaa')); + expect(codec.decode(hex('ffaa'))).toStrictEqual([0xaan, 0xffn]); +}); diff --git a/packages/dynamic-codecs/test/codecs/transforms/SentinelTransformNode.test.ts b/packages/dynamic-codecs/test/codecs/transforms/SentinelTransformNode.test.ts new file mode 100644 index 000000000..b6002cbb1 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/transforms/SentinelTransformNode.test.ts @@ -0,0 +1,12 @@ +import { constantValueNodeFromBytes, sentinelTransformNode, stringTypeNode } from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../../src'; +import { hex } from '../../_setup'; + +test('it encodes sentinel types', () => { + const sentinel = sentinelTransformNode(constantValueNodeFromBytes('base16', 'ffff')); + const codec = getNodeValueCodec([stringTypeNode('utf8', { transforms: [sentinel] })]); + expect(codec.encode('Hello World!')).toStrictEqual(hex('48656c6c6f20576f726c6421ffff')); + expect(codec.decode(hex('48656c6c6f20576f726c6421ffff'))).toBe('Hello World!'); +}); diff --git a/packages/dynamic-codecs/test/codecs/transforms/SizePrefixTransformNode.test.ts b/packages/dynamic-codecs/test/codecs/transforms/SizePrefixTransformNode.test.ts new file mode 100644 index 000000000..aaa78b784 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/transforms/SizePrefixTransformNode.test.ts @@ -0,0 +1,19 @@ +import { integerTypeNode, sizePrefixTransformNode, stringTypeNode } from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../../src'; +import { hex } from '../../_setup'; + +test('it encodes types prefixed with their sizes', () => { + const prefix = sizePrefixTransformNode(integerTypeNode('u32')); + const codec = getNodeValueCodec([stringTypeNode('utf8', { transforms: [prefix] })]); + expect(codec.encode('Hello World!')).toStrictEqual(hex('0c00000048656c6c6f20576f726c6421')); + expect(codec.decode(hex('0c00000048656c6c6f20576f726c6421'))).toBe('Hello World!'); +}); + +test('it encodes big-endian size prefixes', () => { + const prefix = sizePrefixTransformNode(integerTypeNode('u16', { endian: 'be' })); + const codec = getNodeValueCodec([stringTypeNode('utf8', { transforms: [prefix] })]); + expect(codec.encode('Hi')).toStrictEqual(hex('00024869')); + expect(codec.decode(hex('00024869'))).toBe('Hi'); +}); diff --git a/packages/dynamic-codecs/test/codecs/transforms/TransformOrder.test.ts b/packages/dynamic-codecs/test/codecs/transforms/TransformOrder.test.ts new file mode 100644 index 000000000..afbfbe128 --- /dev/null +++ b/packages/dynamic-codecs/test/codecs/transforms/TransformOrder.test.ts @@ -0,0 +1,38 @@ +import { + constantValueNodeFromBytes, + fixedSizeTransformNode, + hiddenPrefixTransformNode, + integerTypeNode, + sizePrefixTransformNode, + stringTypeNode, +} from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { getNodeValueCodec } from '../../../src'; +import { hex } from '../../_setup'; + +test('it applies transforms from the innermost to the outermost', () => { + const fixedSize = fixedSizeTransformNode(4); + const sizePrefix = sizePrefixTransformNode(integerTypeNode('u8')); + + // The size prefix wraps the fixed size, so it is always 4. + const codecA = getNodeValueCodec([stringTypeNode('utf8', { transforms: [fixedSize, sizePrefix] })]); + expect(codecA.encode('Hi')).toStrictEqual(hex('0448690000')); + + // The fixed size wraps the size prefix, so the prefix is part of the 4 bytes. + const codecB = getNodeValueCodec([stringTypeNode('utf8', { transforms: [sizePrefix, fixedSize] })]); + expect(codecB.encode('Hi')).toStrictEqual(hex('02486900')); +}); + +test('it applies several transforms of the same kind', () => { + const codec = getNodeValueCodec([ + stringTypeNode('utf8', { + transforms: [ + hiddenPrefixTransformNode([constantValueNodeFromBytes('base16', 'aa')]), + hiddenPrefixTransformNode([constantValueNodeFromBytes('base16', 'bb')]), + ], + }), + ]); + expect(codec.encode('Hi')).toStrictEqual(hex('bbaa4869')); + expect(codec.decode(hex('bbaa4869'))).toBe('Hi'); +}); diff --git a/packages/dynamic-codecs/test/values/ArrayValueNode.test.ts b/packages/dynamic-codecs/test/values/ArrayValueNode.test.ts index 0fa97cc20..ac2e7a1a9 100644 --- a/packages/dynamic-codecs/test/values/ArrayValueNode.test.ts +++ b/packages/dynamic-codecs/test/values/ArrayValueNode.test.ts @@ -1,11 +1,11 @@ -import { arrayValueNode, numberValueNode } from '@codama/nodes'; +import { arrayValueNode, integerValueNode } from '@codama/nodes'; import { LinkableDictionary, visit } from '@codama/visitors-core'; import { expect, test } from 'vitest'; import { getValueNodeVisitor } from '../../src'; test('it returns an array of all resolved value nodes', () => { - const node = arrayValueNode([numberValueNode(1), numberValueNode(2), numberValueNode(3)]); + const node = arrayValueNode([integerValueNode('1'), integerValueNode('2'), integerValueNode('3')]); const result = visit(node, getValueNodeVisitor(new LinkableDictionary())); - expect(result).toStrictEqual([1, 2, 3]); + expect(result).toStrictEqual([1n, 2n, 3n]); }); diff --git a/packages/dynamic-codecs/test/values/BooleanValueNode.test.ts b/packages/dynamic-codecs/test/values/BooleanValueNode.test.ts new file mode 100644 index 000000000..eb3d8185b --- /dev/null +++ b/packages/dynamic-codecs/test/values/BooleanValueNode.test.ts @@ -0,0 +1,11 @@ +import { booleanValueNode } from '@codama/nodes'; +import { LinkableDictionary, visit } from '@codama/visitors-core'; +import { expect, test } from 'vitest'; + +import { getValueNodeVisitor } from '../../src'; + +test('it returns the boolean as-is', () => { + const visitor = getValueNodeVisitor(new LinkableDictionary()); + expect(visit(booleanValueNode(true), visitor)).toBe(true); + expect(visit(booleanValueNode(false), visitor)).toBe(false); +}); diff --git a/packages/dynamic-codecs/test/values/ConstantValueNode.test.ts b/packages/dynamic-codecs/test/values/ConstantValueNode.test.ts index ebc32ba7f..8cb5240bb 100644 --- a/packages/dynamic-codecs/test/values/ConstantValueNode.test.ts +++ b/packages/dynamic-codecs/test/values/ConstantValueNode.test.ts @@ -4,10 +4,10 @@ import { bytesTypeNode, bytesValueNode, constantValueNode, - fixedSizeTypeNode, + fixedSizeTransformNode, + integerTypeNode, + integerValueNode, noneValueNode, - numberTypeNode, - numberValueNode, optionTypeNode, someValueNode, stringTypeNode, @@ -20,24 +20,24 @@ import { import { LinkableDictionary, visit } from '@codama/visitors-core'; import { expect, test } from 'vitest'; -import { getNodeCodecVisitor, getValueNodeVisitor } from '../../src'; +import { getNodeValueCodecVisitor, getValueNodeVisitor } from '../../src'; -test('it returns bytes from encoded numbers', () => { - const node = constantValueNode(numberTypeNode('u32'), numberValueNode(42)); +test('it returns bytes from encoded integers', () => { + const node = constantValueNode(integerTypeNode('u32'), integerValueNode('42')); const result = visit(node, getValueNodeVisitor(new LinkableDictionary())); expect(result).toStrictEqual(['base64', 'KgAAAA==']); }); test('it uses the default byte encoding from the codec visitor', () => { - const node = constantValueNode(numberTypeNode('u32'), numberValueNode(42)); + const node = constantValueNode(integerTypeNode('u32'), integerValueNode('42')); const linkables = new LinkableDictionary(); - const codecVisitorFactory = () => getNodeCodecVisitor(linkables, { bytesEncoding: 'base16' }); + const codecVisitorFactory = () => getNodeValueCodecVisitor(linkables, { bytesEncoding: 'base16' }); const result = visit(node, getValueNodeVisitor(linkables, { codecVisitorFactory })); expect(result).toStrictEqual(['base16', '2a000000']); }); test('it uses the default byte encoding from the codec visitor options', () => { - const node = constantValueNode(numberTypeNode('u32'), numberValueNode(42)); + const node = constantValueNode(integerTypeNode('u32'), integerValueNode('42')); const result = visit( node, getValueNodeVisitor(new LinkableDictionary(), { codecVisitorOptions: { bytesEncoding: 'base16' } }), @@ -68,12 +68,15 @@ test('it returns bytes from boolean values', () => { test('it returns bytes from struct values', () => { const node = constantValueNode( structTypeNode([ - structFieldTypeNode({ name: 'firstname', type: fixedSizeTypeNode(stringTypeNode('utf8'), 5) }), - structFieldTypeNode({ name: 'age', type: numberTypeNode('u16') }), + structFieldTypeNode({ + identifier: 'firstname', + type: stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(5)] }), + }), + structFieldTypeNode({ identifier: 'age', type: integerTypeNode('u16') }), ]), structValueNode([ structFieldValueNode('firstname', stringValueNode('John')), - structFieldValueNode('age', stringValueNode('42')), + structFieldValueNode('age', integerValueNode('42')), ]), ); const visitor = getValueNodeVisitor(new LinkableDictionary(), { codecVisitorOptions: { bytesEncoding: 'base16' } }); @@ -83,9 +86,9 @@ test('it returns bytes from struct values', () => { test('it returns bytes from option values', () => { const visitor = getValueNodeVisitor(new LinkableDictionary(), { codecVisitorOptions: { bytesEncoding: 'base16' } }); - const type = optionTypeNode(numberTypeNode('u16')); + const type = optionTypeNode(integerTypeNode('u16')); const resultNone = visit(constantValueNode(type, noneValueNode()), visitor); - const resultSome = visit(constantValueNode(type, someValueNode(numberValueNode(42))), visitor); + const resultSome = visit(constantValueNode(type, someValueNode(integerValueNode('42'))), visitor); expect(resultNone).toStrictEqual(['base16', '00']); expect(resultSome).toStrictEqual(['base16', '012a00']); }); diff --git a/packages/dynamic-codecs/test/values/EnumValueNode.test.ts b/packages/dynamic-codecs/test/values/EnumValueNode.test.ts index b19d551f1..4caf0be3b 100644 --- a/packages/dynamic-codecs/test/values/EnumValueNode.test.ts +++ b/packages/dynamic-codecs/test/values/EnumValueNode.test.ts @@ -1,13 +1,12 @@ +import { CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND, CodamaError } from '@codama/errors'; import { definedTypeNode, - enumEmptyVariantTypeNode, - enumStructVariantTypeNode, - enumTupleVariantTypeNode, enumTypeNode, enumValueNode, - fixedSizeTypeNode, - numberTypeNode, - numberValueNode, + enumVariantTypeNode, + fixedSizeTransformNode, + integerTypeNode, + integerValueNode, programNode, rootNode, stringTypeNode, @@ -24,83 +23,85 @@ import { expect, test } from 'vitest'; import { getValueNodeVisitor } from '../../src'; -test('it returns scalar enum values as discriminated unions', () => { - // Given a program with a scalar enum. - const definedType = definedTypeNode({ - name: 'direction', - type: enumTypeNode([ - enumEmptyVariantTypeNode('up'), - enumEmptyVariantTypeNode('right'), - enumEmptyVariantTypeNode('down'), - enumEmptyVariantTypeNode('left'), - ]), - }); - const root = rootNode(programNode({ definedTypes: [definedType], name: 'myProgram', publicKey: '1111' })); - - // And a LinkableDictionary that recorded the enum. +function getVisitorForEnum(type: ReturnType, identifier: string) { + const definedType = definedTypeNode({ identifier, type }); + const root = rootNode(programNode({ definedTypes: [definedType], identifier: 'myProgram', publicKey: '1111' })); const linkables = new LinkableDictionary(); linkables.recordPath([root, root.program, definedType]); + return getValueNodeVisitor(linkables, { stack: new NodeStack([root, root.program]) }); +} - // And a value node visitor that's under the same program. - const stack = new NodeStack([root, root.program]); - const visitor = getValueNodeVisitor(linkables, { stack }); +test('it returns scalar enum values as discriminated unions', () => { + // Given a value visitor under a program with a scalar enum. + const visitor = getVisitorForEnum( + enumTypeNode([ + enumVariantTypeNode('up'), + enumVariantTypeNode('right'), + enumVariantTypeNode('down'), + enumVariantTypeNode('left'), + ]), + 'direction', + ); // When we visit enum value nodes for this enum type. const resultUp = visit(enumValueNode('direction', 'up'), visitor); - const resultRight = visit(enumValueNode('direction', 'right'), visitor); - const resultDown = visit(enumValueNode('direction', 'down'), visitor); const resultLeft = visit(enumValueNode('direction', 'left'), visitor); - // Then we expect the values to be resolved from the linkable type as discriminated unions. - expect(resultUp).toStrictEqual({ __discriminator: 0, __kind: 'Up' }); - expect(resultRight).toStrictEqual({ __discriminator: 1, __kind: 'Right' }); - expect(resultDown).toStrictEqual({ __discriminator: 2, __kind: 'Down' }); - expect(resultLeft).toStrictEqual({ __discriminator: 3, __kind: 'Left' }); + // Then we expect the values to be resolved from the linked type as discriminated unions. + expect(resultUp).toStrictEqual({ __discriminator: 0, __kind: 'up' }); + expect(resultLeft).toStrictEqual({ __discriminator: 3, __kind: 'left' }); }); -test('it returns data enum values as objects', () => { - // Given a program with a data enum. - const definedType = definedTypeNode({ - name: 'action', - type: enumTypeNode([ - enumEmptyVariantTypeNode('quit'), - enumTupleVariantTypeNode('write', tupleTypeNode([fixedSizeTypeNode(stringTypeNode('utf8'), 5)])), - enumStructVariantTypeNode( - 'move', - structTypeNode([ - structFieldTypeNode({ name: 'x', type: numberTypeNode('u8') }), - structFieldTypeNode({ name: 'y', type: numberTypeNode('u8') }), +test('it returns data enum values with their data', () => { + // Given a value visitor under a program with a data enum. + const visitor = getVisitorForEnum( + enumTypeNode([ + enumVariantTypeNode('quit'), + enumVariantTypeNode('write', { + data: tupleTypeNode([stringTypeNode('utf8', { transforms: [fixedSizeTransformNode(5)] })]), + }), + enumVariantTypeNode('move', { + data: structTypeNode([ + structFieldTypeNode({ identifier: 'x', type: integerTypeNode('u8') }), + structFieldTypeNode({ identifier: 'y', type: integerTypeNode('u8') }), ]), - ), + discriminator: 5, + }), ]), - }); - const root = rootNode(programNode({ definedTypes: [definedType], name: 'myProgram', publicKey: '1111' })); - - // And a LinkableDictionary that recorded the enum. - const linkables = new LinkableDictionary(); - linkables.recordPath([root, root.program, definedType]); - - // And a value node visitor that's under the same program. - const stack = new NodeStack([root, root.program]); - const visitor = getValueNodeVisitor(linkables, { stack }); + 'action', + ); // When we visit enum value nodes for this enum type. const resultQuit = visit(enumValueNode('action', 'quit'), visitor); - const resultWrite = visit(enumValueNode('action', 'write', tupleValueNode([stringValueNode('Hello')])), visitor); + const resultWrite = visit( + enumValueNode('action', 'write', { value: tupleValueNode([stringValueNode('Hello')]) }), + visitor, + ); const resultMove = visit( - enumValueNode( - 'action', - 'move', - structValueNode([ - structFieldValueNode('x', numberValueNode(10)), - structFieldValueNode('y', numberValueNode(20)), + enumValueNode('action', 'move', { + value: structValueNode([ + structFieldValueNode('x', integerValueNode('10')), + structFieldValueNode('y', integerValueNode('20')), ]), - ), + }), visitor, ); - // Then we expect the values to be resolved from the linkable type as numbers. - expect(resultQuit).toStrictEqual({ __discriminator: 0, __kind: 'Quit' }); - expect(resultWrite).toStrictEqual({ __discriminator: 1, __kind: 'Write', fields: ['Hello'] }); - expect(resultMove).toStrictEqual({ __discriminator: 2, __kind: 'Move', x: 10, y: 20 }); + // Then we expect the data to be nested under `data`. + expect(resultQuit).toStrictEqual({ __discriminator: 0, __kind: 'quit' }); + expect(resultWrite).toStrictEqual({ __discriminator: 1, __kind: 'write', data: ['Hello'] }); + expect(resultMove).toStrictEqual({ __discriminator: 5, __kind: 'move', data: { x: 10n, y: 20n } }); +}); + +test('it throws when the variant does not exist', () => { + const enumType = enumTypeNode([enumVariantTypeNode('up')]); + const visitor = getVisitorForEnum(enumType, 'direction'); + const node = enumValueNode('direction', 'sideways'); + expect(() => visit(node, visitor)).toThrow( + new CodamaError(CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND, { + enum: enumType, + enumName: node.enum.identifier, + variant: node.variant, + }), + ); }); diff --git a/packages/dynamic-codecs/test/values/FloatValueNode.test.ts b/packages/dynamic-codecs/test/values/FloatValueNode.test.ts new file mode 100644 index 000000000..dcbfb0305 --- /dev/null +++ b/packages/dynamic-codecs/test/values/FloatValueNode.test.ts @@ -0,0 +1,10 @@ +import { floatValueNode } from '@codama/nodes'; +import { LinkableDictionary, visit } from '@codama/visitors-core'; +import { expect, test } from 'vitest'; + +import { getValueNodeVisitor } from '../../src'; + +test('it returns the float as a number', () => { + const result = visit(floatValueNode('1.5'), getValueNodeVisitor(new LinkableDictionary())); + expect(result).toBe(1.5); +}); diff --git a/packages/dynamic-codecs/test/values/InjectedValueNode.test.ts b/packages/dynamic-codecs/test/values/InjectedValueNode.test.ts index bf115ba27..d152d4497 100644 --- a/packages/dynamic-codecs/test/values/InjectedValueNode.test.ts +++ b/packages/dynamic-codecs/test/values/InjectedValueNode.test.ts @@ -1,22 +1,42 @@ -import { CODAMA_ERROR__DYNAMIC_CLIENT__INVARIANT_VIOLATION, isCodamaError } from '@codama/errors'; -import { injectedValueNode } from '@codama/nodes'; -import { LinkableDictionary, visit } from '@codama/visitors-core'; +import { CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, CodamaError } from '@codama/errors'; +import { + injectedValueNode, + integerValueNode, + providedNode, + structFieldValueNode, + structValueNode, +} from '@codama/nodes'; +import { LinkableDictionary, ProvidedScope, visit } from '@codama/visitors-core'; import { expect, test } from 'vitest'; import { getValueNodeVisitor } from '../../src'; -test('it throws when an injectedValueNode reaches codec evaluation unresolved', () => { - // Given an injectedValueNode that the provide/inject pass never replaced with a concrete value. - const node = injectedValueNode({ key: 'decimals' }); +test('it resolves injected values from the scope', () => { + const scope = new ProvidedScope([providedNode('decimals', integerValueNode('9'))]); + const visitor = getValueNodeVisitor(new LinkableDictionary(), { scope }); + expect(visit(injectedValueNode({ key: 'decimals' }), visitor)).toBe(9n); +}); + +test('it resolves nested injected values', () => { + const scope = new ProvidedScope([providedNode('decimals', integerValueNode('9'))]); + const visitor = getValueNodeVisitor(new LinkableDictionary(), { scope }); + const node = structValueNode([structFieldValueNode('decimals', injectedValueNode({ key: 'decimals' }))]); + expect(visit(node, visitor)).toStrictEqual({ decimals: 9n }); +}); - // When we visit it with the value-node codec visitor. - const visitInjectedValue = () => visit(node, getValueNodeVisitor(new LinkableDictionary())); +test('it uses the fallback when the value is not provided', () => { + const visitor = getValueNodeVisitor(new LinkableDictionary()); + const node = injectedValueNode({ fallback: integerValueNode('6'), key: 'decimals' }); + expect(visit(node, visitor)).toBe(6n); +}); - // Then it throws a pipeline invariant-violation error naming the unresolved node. - expect(visitInjectedValue).toThrow(/injectedValueNode \(key "decimals"\) reached codec evaluation/); - try { - visitInjectedValue(); - } catch (error) { - expect(isCodamaError(error, CODAMA_ERROR__DYNAMIC_CLIENT__INVARIANT_VIOLATION)).toBe(true); - } +test('it throws when the value is not provided and has no fallback', () => { + const node = injectedValueNode({ key: 'decimals' }); + const visitor = getValueNodeVisitor(new LinkableDictionary()); + expect(() => visit(node, visitor)).toThrow( + new CodamaError(CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, { + injectedValue: node, + key: node.key, + }), + ); }); diff --git a/packages/dynamic-codecs/test/values/IntegerValueNode.test.ts b/packages/dynamic-codecs/test/values/IntegerValueNode.test.ts new file mode 100644 index 000000000..941c594b5 --- /dev/null +++ b/packages/dynamic-codecs/test/values/IntegerValueNode.test.ts @@ -0,0 +1,15 @@ +import { integerValueNode } from '@codama/nodes'; +import { LinkableDictionary, visit } from '@codama/visitors-core'; +import { expect, test } from 'vitest'; + +import { getValueNodeVisitor } from '../../src'; + +test('it returns the integer as a bigint', () => { + const result = visit(integerValueNode('42'), getValueNodeVisitor(new LinkableDictionary())); + expect(result).toBe(42n); +}); + +test('it keeps the precision of large integers', () => { + const result = visit(integerValueNode('18446744073709551615'), getValueNodeVisitor(new LinkableDictionary())); + expect(result).toBe(18446744073709551615n); +}); diff --git a/packages/dynamic-codecs/test/values/MapValueNode.test.ts b/packages/dynamic-codecs/test/values/MapValueNode.test.ts index fdec84cfc..cf7c81a52 100644 --- a/packages/dynamic-codecs/test/values/MapValueNode.test.ts +++ b/packages/dynamic-codecs/test/values/MapValueNode.test.ts @@ -1,4 +1,4 @@ -import { mapEntryValueNode, mapValueNode, numberValueNode, stringValueNode } from '@codama/nodes'; +import { integerValueNode, mapEntryValueNode, mapValueNode, stringValueNode } from '@codama/nodes'; import { LinkableDictionary, visit } from '@codama/visitors-core'; import { expect, test } from 'vitest'; @@ -6,10 +6,10 @@ import { getValueNodeVisitor } from '../../src'; test('it resolves map value nodes as objects', () => { const node = mapValueNode([ - mapEntryValueNode(stringValueNode('foo'), numberValueNode(1)), - mapEntryValueNode(stringValueNode('bar'), numberValueNode(2)), - mapEntryValueNode(stringValueNode('baz'), numberValueNode(3)), + mapEntryValueNode(stringValueNode('foo'), integerValueNode('1')), + mapEntryValueNode(stringValueNode('bar'), integerValueNode('2')), + mapEntryValueNode(stringValueNode('baz'), integerValueNode('3')), ]); const result = visit(node, getValueNodeVisitor(new LinkableDictionary())); - expect(result).toStrictEqual({ bar: 2, baz: 3, foo: 1 }); + expect(result).toStrictEqual({ bar: 2n, baz: 3n, foo: 1n }); }); diff --git a/packages/dynamic-codecs/test/values/NumberValueNode.test.ts b/packages/dynamic-codecs/test/values/NumberValueNode.test.ts deleted file mode 100644 index a820b6be6..000000000 --- a/packages/dynamic-codecs/test/values/NumberValueNode.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { numberValueNode } from '@codama/nodes'; -import { LinkableDictionary, visit } from '@codama/visitors-core'; -import { expect, test } from 'vitest'; - -import { getValueNodeVisitor } from '../../src'; - -test('it returns the number as-is', () => { - const result = visit(numberValueNode(42), getValueNodeVisitor(new LinkableDictionary())); - expect(result).toBe(42); -}); diff --git a/packages/dynamic-codecs/test/values/SetValueNode.test.ts b/packages/dynamic-codecs/test/values/SetValueNode.test.ts index 13c35b00c..e1efdd117 100644 --- a/packages/dynamic-codecs/test/values/SetValueNode.test.ts +++ b/packages/dynamic-codecs/test/values/SetValueNode.test.ts @@ -1,11 +1,11 @@ -import { numberValueNode, setValueNode } from '@codama/nodes'; +import { integerValueNode, setValueNode } from '@codama/nodes'; import { LinkableDictionary, visit } from '@codama/visitors-core'; import { expect, test } from 'vitest'; import { getValueNodeVisitor } from '../../src'; test('it returns an array of all resolved value nodes', () => { - const node = setValueNode([numberValueNode(1), numberValueNode(2), numberValueNode(3)]); + const node = setValueNode([integerValueNode('1'), integerValueNode('2'), integerValueNode('3')]); const result = visit(node, getValueNodeVisitor(new LinkableDictionary())); - expect(result).toStrictEqual([1, 2, 3]); + expect(result).toStrictEqual([1n, 2n, 3n]); }); diff --git a/packages/dynamic-codecs/test/values/StructValueNode.test.ts b/packages/dynamic-codecs/test/values/StructValueNode.test.ts index 26c8daaa7..c38cb24a1 100644 --- a/packages/dynamic-codecs/test/values/StructValueNode.test.ts +++ b/packages/dynamic-codecs/test/values/StructValueNode.test.ts @@ -1,4 +1,4 @@ -import { numberValueNode, stringValueNode, structFieldValueNode, structValueNode } from '@codama/nodes'; +import { integerValueNode, stringValueNode, structFieldValueNode, structValueNode } from '@codama/nodes'; import { LinkableDictionary, visit } from '@codama/visitors-core'; import { expect, test } from 'vitest'; @@ -7,8 +7,14 @@ import { getValueNodeVisitor } from '../../src'; test('it returns struct values as objects', () => { const node = structValueNode([ structFieldValueNode('firstname', stringValueNode('John')), - structFieldValueNode('age', numberValueNode(42)), + structFieldValueNode('age', integerValueNode('42')), ]); const result = visit(node, getValueNodeVisitor(new LinkableDictionary())); - expect(result).toStrictEqual({ age: 42, firstname: 'John' }); + expect(result).toStrictEqual({ age: 42n, firstname: 'John' }); +}); + +test('it uses the raw field identifiers', () => { + const node = structValueNode([structFieldValueNode('my_field', integerValueNode('42'))]); + const result = visit(node, getValueNodeVisitor(new LinkableDictionary())); + expect(result).toStrictEqual({ my_field: 42n }); }); diff --git a/packages/dynamic-codecs/test/values/TupleValueNode.test.ts b/packages/dynamic-codecs/test/values/TupleValueNode.test.ts index 26f6215df..15c0ca4af 100644 --- a/packages/dynamic-codecs/test/values/TupleValueNode.test.ts +++ b/packages/dynamic-codecs/test/values/TupleValueNode.test.ts @@ -1,11 +1,11 @@ -import { booleanValueNode, numberValueNode, stringValueNode, tupleValueNode } from '@codama/nodes'; +import { booleanValueNode, integerValueNode, stringValueNode, tupleValueNode } from '@codama/nodes'; import { LinkableDictionary, visit } from '@codama/visitors-core'; import { expect, test } from 'vitest'; import { getValueNodeVisitor } from '../../src'; test('it returns the tuple as an array of values', () => { - const node = tupleValueNode([numberValueNode(42), stringValueNode('Hello'), booleanValueNode(true)]); + const node = tupleValueNode([integerValueNode('42'), stringValueNode('Hello'), booleanValueNode(true)]); const result = visit(node, getValueNodeVisitor(new LinkableDictionary())); - expect(result).toStrictEqual([42, 'Hello', true]); + expect(result).toStrictEqual([42n, 'Hello', true]); }); diff --git a/packages/dynamic-instructions/package.json b/packages/dynamic-instructions/package.json index 341dc4bee..174648677 100644 --- a/packages/dynamic-instructions/package.json +++ b/packages/dynamic-instructions/package.json @@ -77,16 +77,16 @@ "@codama/dynamic-codecs": "workspace:*", "@codama/dynamic-parsers": "workspace:*", "@codama/errors": "workspace:*", - "@solana/accounts": "^8.3.0", - "@solana/addresses": "^8.3.0", - "@solana/codecs": "^8.3.0", - "@solana/instructions": "^8.3.0", + "@solana/accounts": "^8.4.0", + "@solana/addresses": "^8.4.0", + "@solana/codecs": "^8.4.0", + "@solana/instructions": "^8.4.0", "codama": "workspace:*", "commander": "^15.0.0", "superstruct": "^2.0.2" }, "devDependencies": { - "@solana/kit": "8.3.0" + "@solana/kit": "8.4.0" }, "browserslist": [ "supports bigint and not dead", diff --git a/packages/dynamic-parsers/package.json b/packages/dynamic-parsers/package.json index f75ec8bb9..d83019e82 100644 --- a/packages/dynamic-parsers/package.json +++ b/packages/dynamic-parsers/package.json @@ -56,10 +56,10 @@ "@codama/errors": "workspace:*", "@codama/nodes": "workspace:*", "@codama/visitors-core": "workspace:*", - "@solana/instructions": "^8.3.0" + "@solana/instructions": "^8.4.0" }, "devDependencies": { - "@solana/codecs": "^8.3.0" + "@solana/codecs": "^8.4.0" }, "browserslist": [ "supports bigint and not dead", diff --git a/packages/errors/src/codes.ts b/packages/errors/src/codes.ts index 3ef36e65b..42d39b7b8 100644 --- a/packages/errors/src/codes.ts +++ b/packages/errors/src/codes.ts @@ -39,6 +39,7 @@ export const CODAMA_ERROR__DISCRIMINATOR_FIELD_NOT_FOUND = 10; export const CODAMA_ERROR__DISCRIMINATOR_FIELD_HAS_NO_DEFAULT_VALUE = 11; export const CODAMA_ERROR__UNSUPPORTED_VERSION = 12; export const CODAMA_ERROR__INVALID_BRANDED_STRING = 13; +export const CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED = 14; // Visitors-related errors. // Reserve error codes in the range [1200000-1200999]. @@ -149,6 +150,7 @@ export type CodamaErrorCode = | typeof CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_NODE | typeof CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_OPTIONAL_ACCOUNT_STRATEGY | typeof CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND + | typeof CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED | typeof CODAMA_ERROR__INVALID_BRANDED_STRING | typeof CODAMA_ERROR__LINKED_NODE_NOT_FOUND | typeof CODAMA_ERROR__NODE_FILESYSTEM_FUNCTION_UNAVAILABLE diff --git a/packages/errors/src/context.ts b/packages/errors/src/context.ts index 570ebe09a..161c5296b 100644 --- a/packages/errors/src/context.ts +++ b/packages/errors/src/context.ts @@ -10,6 +10,7 @@ import { DefinedTypeNode, EnumTypeNode, IdentifierString, + InjectedValueNode, InstructionAccountNode, InstructionNode, LinkNode, @@ -55,6 +56,7 @@ import { CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_NODE, CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_OPTIONAL_ACCOUNT_STRATEGY, CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND, + CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, CODAMA_ERROR__INVALID_BRANDED_STRING, CODAMA_ERROR__LINKED_NODE_NOT_FOUND, CODAMA_ERROR__NODE_FILESYSTEM_FUNCTION_UNAVAILABLE, @@ -215,6 +217,10 @@ export type CodamaErrorContext = DefaultUnspecifiedErrorContextToUndefined<{ enumName: IdentifierString; variant: IdentifierString; }; + [CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED]: { + injectedValue: InjectedValueNode; + key: IdentifierString; + }; [CODAMA_ERROR__INVALID_BRANDED_STRING]: { actual: string; expected: string; diff --git a/packages/errors/src/messages.ts b/packages/errors/src/messages.ts index c3752a294..d4b94e58e 100644 --- a/packages/errors/src/messages.ts +++ b/packages/errors/src/messages.ts @@ -35,6 +35,7 @@ import { CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_NODE, CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_OPTIONAL_ACCOUNT_STRATEGY, CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND, + CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, CODAMA_ERROR__INVALID_BRANDED_STRING, CODAMA_ERROR__LINKED_NODE_NOT_FOUND, CODAMA_ERROR__NODE_FILESYSTEM_FUNCTION_UNAVAILABLE, @@ -122,6 +123,8 @@ export const CodamaErrorMessages: Readonly<{ [CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_OPTIONAL_ACCOUNT_STRATEGY]: 'Unsupported optional account strategy [$strategy] for account [$accountName] in [$instructionName].', [CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND]: 'Enum variant [$variant] not found in enum type [$enumName].', + [CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED]: + 'Injected value [$key] is not provided and has no fallback. Values injected into a defined type are provided by the instruction that uses it.', [CODAMA_ERROR__INVALID_BRANDED_STRING]: 'Expected a valid [$expected], got [$actual].', [CODAMA_ERROR__LINKED_NODE_NOT_FOUND]: 'Could not find linked node [$name] from [$kind].', [CODAMA_ERROR__NODE_FILESYSTEM_FUNCTION_UNAVAILABLE]: diff --git a/packages/nodes-from-anchor/package.json b/packages/nodes-from-anchor/package.json index fac6c54b8..2c1d7eaf8 100644 --- a/packages/nodes-from-anchor/package.json +++ b/packages/nodes-from-anchor/package.json @@ -56,7 +56,7 @@ "@codama/nodes": "workspace:*", "@codama/visitors": "workspace:*", "@noble/hashes": "^2.4.0", - "@solana/codecs": "^8.3.0" + "@solana/codecs": "^8.4.0" }, "browserslist": [ "supports bigint and not dead", diff --git a/packages/visitors-core/src/ProvidedScope.ts b/packages/visitors-core/src/ProvidedScope.ts index 41d6ce672..93e5d4b30 100644 --- a/packages/visitors-core/src/ProvidedScope.ts +++ b/packages/visitors-core/src/ProvidedScope.ts @@ -1,4 +1,8 @@ -import { CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, CodamaError } from '@codama/errors'; +import { + CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, + CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, + CodamaError, +} from '@codama/errors'; import { assertIsNode, type GetNodeFromKind, @@ -16,6 +20,9 @@ import { visit } from './visitor'; /** A node an injection resolved to, with the frame depth its own injections resolve against. */ type Resolution = { depth: number; node: Node; provider?: ProvidedNode }; +/** The outcome of resolving a node: the resolved node, or the first injection that resolved to nothing. */ +type Outcome = { missing: InjectedValueNode } | { node: Node }; + /** * The lexical scope of `providedNode`s visible at a point of the tree, * used to resolve `injectedValueNode`s. @@ -73,26 +80,56 @@ export class ProvidedScope { node: Node, options: { kinds: TKind[] }, ): Exclude, InjectedValueNode> | undefined { + const outcome = this.resolveOutcome(node, options.kinds); + if ('missing' in outcome) return undefined; + return outcome.node as Exclude, InjectedValueNode>; + } + + /** + * Same as {@link ProvidedScope.resolve}, but throws + * `CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED` instead of returning + * `undefined`. The error names the first injection that resolves to nothing. + * + * Use it where the value is required, e.g. to encode a constant. Where an + * absent value is acceptable, e.g. an optional display attribute or an + * injection within a defined type used outside of any instruction, use + * {@link ProvidedScope.resolve} instead. + */ + public resolveOrThrow( + node: Node, + options: { kinds: TKind[] }, + ): Exclude, InjectedValueNode> { + const outcome = this.resolveOutcome(node, options.kinds); + if ('missing' in outcome) { + throw new CodamaError(CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, { + injectedValue: outcome.missing, + key: outcome.missing.key, + }); + } + return outcome.node as Exclude, InjectedValueNode>; + } + + public clone(): ProvidedScope { + return new ProvidedScope(...this.frames); + } + + /** Shared implementation of {@link ProvidedScope.resolve} and {@link ProvidedScope.resolveOrThrow}. */ + private resolveOutcome(node: Node, kinds: TKind[]): Outcome { const depth = this.frames.length; const resolution = isNode(node, 'injectedValueNode') ? this.lookup(node, depth) : { depth, node }; - if (!resolution) return undefined; - if (!isNode(resolution.node, options.kinds)) { + if (!resolution) return { missing: node as InjectedValueNode }; + if (!isNode(resolution.node, kinds)) { if (resolution.provider) { throw new CodamaError(CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, { - expectedKinds: options.kinds, + expectedKinds: kinds, key: resolution.provider.identifier, providedKind: resolution.node.kind, provider: resolution.provider, }); } - assertIsNode(resolution.node, options.kinds); + assertIsNode(resolution.node, kinds); } - const resolved = this.resolveWithin(resolution.node, resolution.depth); - return (resolved ?? undefined) as Exclude, InjectedValueNode> | undefined; - } - - public clone(): ProvidedScope { - return new ProvidedScope(...this.frames); + return this.resolveWithin(resolution.node, resolution.depth); } /** Find the node an injection stands for, searching frames strictly below `depth`. */ @@ -114,25 +151,32 @@ export class ProvidedScope { /** * Replace every injection nested within `node`, resolving against frames - * strictly below `depth`. Returns `null` when any of them is unresolvable, - * and `node` itself when it contains none. + * strictly below `depth`. Returns the first injection that resolves to + * nothing, if any, and `node` itself when it contains no injection. */ - private resolveWithin(node: Node, depth: number): Node | null { + private resolveWithin(node: Node, depth: number): Outcome { let replaced = false; - let unresolvable = false; + let missing: InjectedValueNode | undefined; const visitor = bottomUpTransformerVisitor([ child => { - if (unresolvable || !isNode(child, 'injectedValueNode')) return child; + if (missing || !isNode(child, 'injectedValueNode')) return child; replaced = true; const resolution = this.lookup(child, depth); - const resolved = resolution ? this.resolveWithin(resolution.node, resolution.depth) : null; - if (resolved === null) unresolvable = true; - return resolved; + if (!resolution) { + missing = child; + return null; + } + const outcome = this.resolveWithin(resolution.node, resolution.depth); + if ('missing' in outcome) { + missing = outcome.missing; + return null; + } + return outcome.node; }, ]); const result = visit(node, visitor); - if (unresolvable) return null; - return replaced ? result : node; + if (missing) return { missing }; + return { node: replaced ? (result as Node) : node }; } /** Search frames strictly below `depth`, innermost first. */ diff --git a/packages/visitors-core/src/getByteSizeVisitor.ts b/packages/visitors-core/src/getByteSizeVisitor.ts index 59b9acc22..2afc28e5c 100644 --- a/packages/visitors-core/src/getByteSizeVisitor.ts +++ b/packages/visitors-core/src/getByteSizeVisitor.ts @@ -101,9 +101,11 @@ export function getByteSizeVisitor( if (definedTypeStack.includes(linkedDefinedType.identifier)) return null; stack.pushPath(linkedDefinedPath); - const result = visit(linkedDefinedType, self); - stack.popPath(); - return result; + try { + return visit(linkedDefinedType, self); + } finally { + stack.popPath(); + } }, visitEnumType(node, { self }) { diff --git a/packages/visitors-core/src/getInstructionDataFields.ts b/packages/visitors-core/src/getInstructionDataFields.ts index 1ed0c9ad9..7dd7daaf5 100644 --- a/packages/visitors-core/src/getInstructionDataFields.ts +++ b/packages/visitors-core/src/getInstructionDataFields.ts @@ -49,8 +49,11 @@ export function getInstructionDataFields( if (walkedDefinedTypes.has(definedType)) return; walkedDefinedTypes.add(definedType); stack.pushPath(linkedPath); - walk(definedType.type, prefix); - stack.popPath(); + try { + walk(definedType.type, prefix); + } finally { + stack.popPath(); + } return; } if (!isNode(type, 'structTypeNode')) return; diff --git a/packages/visitors-core/src/getMaxByteSizeVisitor.ts b/packages/visitors-core/src/getMaxByteSizeVisitor.ts index 424a82a22..9365d5e87 100644 --- a/packages/visitors-core/src/getMaxByteSizeVisitor.ts +++ b/packages/visitors-core/src/getMaxByteSizeVisitor.ts @@ -94,9 +94,11 @@ export function getMaxByteSizeVisitor( if (definedTypeStack.includes(linkedDefinedType.identifier)) return null; stack.pushPath(linkedDefinedPath); - const result = visit(linkedDefinedType, self); - stack.popPath(); - return result; + try { + return visit(linkedDefinedType, self); + } finally { + stack.popPath(); + } }, visitEnumType(node, { self }) { diff --git a/packages/visitors-core/src/recordNodeStackVisitor.ts b/packages/visitors-core/src/recordNodeStackVisitor.ts index a0fb39376..6ab67c907 100644 --- a/packages/visitors-core/src/recordNodeStackVisitor.ts +++ b/packages/visitors-core/src/recordNodeStackVisitor.ts @@ -10,8 +10,10 @@ export function recordNodeStackVisitor( ): Visitor { return interceptVisitor(visitor, (node, next) => { stack.push(node); - const newNode = next(node); - stack.pop(); - return newNode; + try { + return next(node); + } finally { + stack.pop(); + } }); } diff --git a/packages/visitors-core/test/ProvidedScope.test.ts b/packages/visitors-core/test/ProvidedScope.test.ts index 79352942c..ae13a98d2 100644 --- a/packages/visitors-core/test/ProvidedScope.test.ts +++ b/packages/visitors-core/test/ProvidedScope.test.ts @@ -1,4 +1,5 @@ import { + CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, CODAMA_ERROR__UNEXPECTED_NODE_KIND, CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, CodamaError, @@ -168,6 +169,61 @@ test('it throws when the provided node is not one of the expected kinds', () => ); }); +test('resolveOrThrow resolves an injection to its provided node', () => { + const scope = new ProvidedScope([providedNode('decimals', integerValueNode('6'))]); + expect(scope.resolveOrThrow(injectedValueNode({ key: 'decimals' }), { kinds })).toEqual(integerValueNode('6')); +}); + +test('resolveOrThrow returns the fallback when no frame provides the key', () => { + const node = injectedValueNode({ fallback: integerValueNode('9'), key: 'decimals' }); + expect(new ProvidedScope().resolveOrThrow(node, { kinds })).toEqual(integerValueNode('9')); +}); + +test('resolveOrThrow throws when no frame provides the key and there is no fallback', () => { + const node = injectedValueNode({ key: 'decimals' }); + expect(() => new ProvidedScope().resolveOrThrow(node, { kinds })).toThrow( + new CodamaError(CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, { injectedValue: node, key: node.key }), + ); +}); + +test('resolveOrThrow names the nested injection that resolves to nothing', () => { + // Given a struct value with an injected field that nothing provides. + const age = injectedValueNode({ key: 'age' }); + const node = structValueNode([ + structFieldValueNode('name', stringValueNode('Alice')), + structFieldValueNode('age', age), + ]); + + // Then the error names the nested injection rather than the struct. + expect(() => new ProvidedScope().resolveOrThrow(node, { kinds: VALUE_NODES })).toThrow( + new CodamaError(CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, { injectedValue: age, key: age.key }), + ); +}); + +test('resolveOrThrow names the injection a provided chain dead-ends on', () => { + // Given a frame that provides a struct injecting a key nobody provides. + const missing = injectedValueNode({ key: 'missing' }); + const scope = new ProvidedScope([providedNode('person', structValueNode([structFieldValueNode('age', missing)]))]); + + // Then the error names the innermost unresolvable injection. + expect(() => scope.resolveOrThrow(injectedValueNode({ key: 'person' }), { kinds: VALUE_NODES })).toThrow( + new CodamaError(CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED, { injectedValue: missing, key: missing.key }), + ); +}); + +test('resolveOrThrow throws when the provided node is not one of the expected kinds', () => { + const provider = providedNode('decimals', publicKeyTypeNode()); + const scope = new ProvidedScope([provider]); + expect(() => scope.resolveOrThrow(injectedValueNode({ key: 'decimals' }), { kinds })).toThrow( + new CodamaError(CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, { + expectedKinds: kinds, + key: identifierString('decimals'), + providedKind: 'publicKeyTypeNode', + provider, + }), + ); +}); + test('it pushes and pops frames', () => { const scope = new ProvidedScope(); scope.push([providedNode('symbol', stringValueNode('SOL'))]); diff --git a/packages/visitors-core/test/recordNodeStackVisitor.test.ts b/packages/visitors-core/test/recordNodeStackVisitor.test.ts index 5a12e8993..f1ae28c28 100644 --- a/packages/visitors-core/test/recordNodeStackVisitor.test.ts +++ b/packages/visitors-core/test/recordNodeStackVisitor.test.ts @@ -54,3 +54,26 @@ test('it includes the current node when applied last', () => { expect(numberStacks.length).toBe(1); expect(numberStacks[0].getPath()).toEqual([node, node.type, ((node.type as TupleTypeNode).items ?? [])[0]]); }); + +test('it restores the node stack when the visit throws', () => { + // Given a tree and a visitor that throws when it reaches a nested node. + const node = definedTypeNode({ + identifier: 'myType', + type: tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]), + }); + const stack = new NodeStack(); + const visitor = pipe( + voidVisitor(), + v => recordNodeStackVisitor(v, stack), + v => + tapVisitor(v, 'integerTypeNode', () => { + throw new Error('boom'); + }), + ); + + // When the visit throws. + expect(() => visit(node, visitor)).toThrow('boom'); + + // Then the node stack is empty again. + expect(stack.isEmpty()).toBe(true); +}); diff --git a/packages/visitors/src/unwrapDefinedTypesVisitor.ts b/packages/visitors/src/unwrapDefinedTypesVisitor.ts index 1faea68a8..800c7fd94 100644 --- a/packages/visitors/src/unwrapDefinedTypesVisitor.ts +++ b/packages/visitors/src/unwrapDefinedTypesVisitor.ts @@ -1,4 +1,4 @@ -import { assertIsNode, IdentifierString, programNode, TYPE_NODES } from '@codama/nodes'; +import { assertIsNode, IdentifierString, Node, programNode, TYPE_NODES } from '@codama/nodes'; import { extendVisitor, findProgramNodeFromPath, @@ -49,8 +49,12 @@ export function unwrapDefinedTypesVisitor(typesToInline: string[] | '*' = '*') { const definedType = getLastNodeFromPath(definedTypePath); stack.pushPath(definedTypePath); - const type = visit(definedType.type, self); - stack.popPath(); + let type: Node; + try { + type = visit(definedType.type, self); + } finally { + stack.popPath(); + } assertIsNode(type, TYPE_NODES); return inlineDefinedType(link, type, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9afc6e30f..352420d51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,11 +91,11 @@ importers: specifier: workspace:* version: link:../errors '@solana/addresses': - specifier: ^8.3.0 - version: 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) '@solana/codecs': - specifier: ^8.3.0 - version: 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) codama: specifier: workspace:* version: link:../library @@ -104,8 +104,8 @@ importers: version: 15.0.0 devDependencies: '@solana/kit': - specifier: 8.3.0 - version: 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + specifier: 8.4.0 + version: 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) packages/dynamic-client: dependencies: @@ -122,14 +122,14 @@ importers: specifier: workspace:* version: link:../errors '@solana/addresses': - specifier: ^8.3.0 - version: 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) '@solana/codecs': - specifier: ^8.3.0 - version: 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) '@solana/instructions': - specifier: ^8.3.0 - version: 8.3.0(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(typescript@7.0.2) codama: specifier: workspace:* version: link:../library @@ -151,22 +151,22 @@ importers: version: 0.0.3(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) '@solana-program/program-metadata': specifier: ^0.9.3 - version: 0.9.3(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + version: 0.9.3(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) '@solana-program/token': specifier: 0.16.1 - version: 0.16.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + version: 0.16.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) '@solana-program/token-2022': specifier: ^0.17.0 - version: 0.17.0(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))(@solana/sysvars@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)) + version: 0.17.0(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))(@solana/sysvars@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)) '@solana/kit': - specifier: 8.3.0 - version: 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + specifier: 8.4.0 + version: 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) litesvm: specifier: 1.4.1 version: 1.4.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) sas-lib: specifier: 2.0.0-beta.1 - version: 2.0.0-beta.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + version: 2.0.0-beta.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) packages/dynamic-codecs: dependencies: @@ -183,8 +183,12 @@ importers: specifier: workspace:* version: link:../visitors-core '@solana/codecs': - specifier: ^8.3.0 - version: 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + devDependencies: + '@solana/errors': + specifier: ^8.4.0 + version: 8.4.0(typescript@7.0.2) packages/dynamic-instructions: dependencies: @@ -201,17 +205,17 @@ importers: specifier: workspace:* version: link:../errors '@solana/accounts': - specifier: ^8.3.0 - version: 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) '@solana/addresses': - specifier: ^8.3.0 - version: 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) '@solana/codecs': - specifier: ^8.3.0 - version: 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) '@solana/instructions': - specifier: ^8.3.0 - version: 8.3.0(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(typescript@7.0.2) codama: specifier: workspace:* version: link:../library @@ -223,8 +227,8 @@ importers: version: 2.0.2 devDependencies: '@solana/kit': - specifier: 8.3.0 - version: 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + specifier: 8.4.0 + version: 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) packages/dynamic-parsers: dependencies: @@ -241,12 +245,12 @@ importers: specifier: workspace:* version: link:../visitors-core '@solana/instructions': - specifier: ^8.3.0 - version: 8.3.0(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(typescript@7.0.2) devDependencies: '@solana/codecs': - specifier: ^8.3.0 - version: 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) packages/errors: dependencies: @@ -316,8 +320,8 @@ importers: specifier: ^2.4.0 version: 2.4.0 '@solana/codecs': - specifier: ^8.3.0 - version: 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + specifier: ^8.4.0 + version: 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) packages/renderers-core: dependencies: @@ -1442,8 +1446,8 @@ packages: typescript: optional: true - '@solana/accounts@8.3.0': - resolution: {integrity: sha512-HNVCs4yyE5kTRHVD3ApuKuByuO85GHE6Ns7I/yMB9vXhFgZjGzFhOGi/YCjlWpIk3RqTyX7wGniHyEb49R3ELQ==} + '@solana/accounts@8.4.0': + resolution: {integrity: sha512-YklERWeG5UD2fzxR0zwWgzCVyuHRqg6eTIyGBAe8hOWIim8TYS1aPBVFAlVkSZiAXaFRxmu1E3IRfeCe/QSQmg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1460,8 +1464,8 @@ packages: typescript: optional: true - '@solana/addresses@8.3.0': - resolution: {integrity: sha512-HSAPbu5TpHYZqUo9SBALvDeOyfU1Cdtk9PCmxv+YjclNuDVbh+yMnesZ614zjhKJ8clvyidWSgmISjlFCJv6qA==} + '@solana/addresses@8.4.0': + resolution: {integrity: sha512-5nF1AAaCtmaSEHcFIIs6TPMIWplaYBxeJ2ZR0z5oPIY+fjpWb9lxCuJyYQxYKHIvp4OdSiueW0q11yXrnyqM+A==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1478,8 +1482,8 @@ packages: typescript: optional: true - '@solana/assertions@8.3.0': - resolution: {integrity: sha512-6NI79X6VANpq1b1BAnQevCs63dW4l3XKSbikVd5k8mlIsURlCTFdtrbXfQInqsUWZJWElf7ZLQu7RkbBOj6wuw==} + '@solana/assertions@8.4.0': + resolution: {integrity: sha512-Pg/PKw/fA+KIHp6X6aGx4fIkr6S7k7wUw7v8Rkr3TkY4tzrbjZDnXEgv1/8dxGh+IX3UNb+5stD8rtAAscxW1g==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1496,8 +1500,8 @@ packages: typescript: optional: true - '@solana/codecs-core@8.3.0': - resolution: {integrity: sha512-rpt01CPeF4FhTTJHyr0Tr4n8U1shYPhe7q2mcmTYyKPATYAkyb5CLHnYKnN6LO71EZSFq3Y2Wm27rwWcBvAJzg==} + '@solana/codecs-core@8.4.0': + resolution: {integrity: sha512-HOsIB3Ic/qnlXv4V0Q5gpxFxBy5pBiINpEMtCzGtHRnj7mI3Amsh8HKR7x9nbP1SBDuDlOrP/Mds9yIqyKn8TQ==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1514,8 +1518,8 @@ packages: typescript: optional: true - '@solana/codecs-data-structures@8.3.0': - resolution: {integrity: sha512-ojwHBnDia6VtkoCzppJGXJ1JZkQ4teYToTRu0e4N5dEkZlFU5EmpeX+1xsw7gu4bR9cNXXMWbWw4uFgVM8Nd6Q==} + '@solana/codecs-data-structures@8.4.0': + resolution: {integrity: sha512-qx9EnKGZzpiNonUoYNMZC4gfw86jfu4uShh0bcWb1o9dPU1kgZ/xCsCgTRf/5WEPZU2fbPNGtvVMr4dKWCTjzw==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1532,8 +1536,8 @@ packages: typescript: optional: true - '@solana/codecs-numbers@8.3.0': - resolution: {integrity: sha512-12u+Y9ERE+gqraV1g0BP/5QxbamukFub05kyj3d3s6V/26ZEby6ReHJ8yuKtPl9fIUnfoQ2M4+th/7u5+5v14w==} + '@solana/codecs-numbers@8.4.0': + resolution: {integrity: sha512-A9SlaBylkTFjtBiVrFUX3We5SZK4L/Q6SKpPyazZQ1MTCO0FyTuKyZwDART2Pzg+C/5xLWjSns+FSTR8FhD/Rg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1553,8 +1557,8 @@ packages: typescript: optional: true - '@solana/codecs-strings@8.3.0': - resolution: {integrity: sha512-S7rZrc1B90AS0QVRlo6+6cX2t+0TGtsadsPWdfA7tXcaDKQfQn4C/6OJxJ7/n8ugw8LMgGC+Xcs81ii4nIp6aQ==} + '@solana/codecs-strings@8.4.0': + resolution: {integrity: sha512-jQhRrJT3ZoeDoSA5DtGxPmI5ODJi3STTCrtIXIniLr1J1cXUSlPfYe8BKOurNwC+SlEJyRmKfpVze+qeLacmmw==} engines: {node: '>=20.18.0'} peerDependencies: fastestsmallesttextencoderdecoder: ^1.0.22 @@ -1574,8 +1578,8 @@ packages: typescript: optional: true - '@solana/codecs@8.3.0': - resolution: {integrity: sha512-BtiHdl7pDw6tadQAn3Vi1s0wgp0BGQ3ndTQRBtxuwmyCzlGfyuvMBq+ynkuGWwknvOpU2eTNEO84U/b7wqLB7w==} + '@solana/codecs@8.4.0': + resolution: {integrity: sha512-mY7CpMQ4nbecxQIyvslEw7nEreiY0QVvg+aZOPbrBjIiZxu2qj9fyhCrDXJdQlcJ+aYh1sInbR8KARpmHgJLQg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1593,8 +1597,8 @@ packages: typescript: optional: true - '@solana/errors@8.3.0': - resolution: {integrity: sha512-XsoQ5ThBOwfMz1KS7I2zZoduLxUYFe+NHuRAm/gt+uk44nnXqwzNJHo/9GEMqJqxafViqvfRZ35GF1fDkwW32Q==} + '@solana/errors@8.4.0': + resolution: {integrity: sha512-t59ageeK8xa6LrCzj93QEhYtVr5t3GdnTH3qJMGqrCFMYM6IvNFLljlz4N75X+xYktj3sRABTrNoCfekeQlckA==} engines: {node: '>=20.18.0'} hasBin: true peerDependencies: @@ -1612,8 +1616,8 @@ packages: typescript: optional: true - '@solana/fast-stable-stringify@8.3.0': - resolution: {integrity: sha512-QVVSFQPLJaFeUFekWHe04TUr5rL2QbgPCEK0HcmlOLgCWTvduwj1D9e73jjYzL5HU1QRyUfzkw+GnSw95nvjLg==} + '@solana/fast-stable-stringify@8.4.0': + resolution: {integrity: sha512-XKVKeocf0T9hjmUHKrOG+exxfZmv+IPQrdkvuTdegyB6V5X4aOlR6IfrJQiTMsJx6IY4EAW3aisxTgZmH5OlYg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1630,8 +1634,8 @@ packages: typescript: optional: true - '@solana/fixed-points@8.3.0': - resolution: {integrity: sha512-cah+OuTSXqSMRlTQdj3sQmf7RK1m2RG50RzSgLFWeAAl+WCsoR4pyGPFxhQMw9JdaniqOUkKKB3ppxTMgfoHEg==} + '@solana/fixed-points@8.4.0': + resolution: {integrity: sha512-YzWZDPn3B6o3j+leuOhxH7Soip12NJVx5lJaRpEeBB4AcJczTiKGCQMSsxQglGSRHc0jwfrH4+Uxla1BlCkgBg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1648,8 +1652,8 @@ packages: typescript: optional: true - '@solana/functional@8.3.0': - resolution: {integrity: sha512-HBtC8tA8q73JQYHFaNROGiBLcwPcbQHldN7f1NqCgYY9x/iIt/zvWPeoxiRkncYCE9MwDLyWMq3LnetjqOifOA==} + '@solana/functional@8.4.0': + resolution: {integrity: sha512-q5pEoqAtEGFsJXK3mnlemLinzRJHvOMjGmuLoWU6c33NQHjfpTEz80hjnRA25BABMLC0l/vhymeLh+YhUv/szw==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1666,8 +1670,8 @@ packages: typescript: optional: true - '@solana/instruction-plans@8.3.0': - resolution: {integrity: sha512-ltuS+fTWsNQokFxK8StQzn/ZDkrR/P4Pao5EpQxYHPQQ+Z83g61kWFEnR+bB9WBDUZwu2CJptNXItKSgGOWwmg==} + '@solana/instruction-plans@8.4.0': + resolution: {integrity: sha512-+qY9d2Jzt2uxzjXs/ZZi448MbpMH0fiB3pWy8krUbCxW8eTl8tMuqIZvltMe05FldFR432xi6xFeMVl0Bw4CWA==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1684,8 +1688,8 @@ packages: typescript: optional: true - '@solana/instructions@8.3.0': - resolution: {integrity: sha512-piAAqqC5NEvN2Mf2FP6PWogl8qCVg/2cEt+LKyq89P0sNbaeMPK11wGcZR6e3LEBMBSnjAw4DbDerDWg6GC0eg==} + '@solana/instructions@8.4.0': + resolution: {integrity: sha512-muT/BBKDTl0UsHsnKySVaNBWIcHbs8nn8nOd1GI9J0OvX4gV/pNtaJfrEW+ETX9SjcIB4AcBQZsIr3LjavvrKQ==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1702,8 +1706,8 @@ packages: typescript: optional: true - '@solana/keys@8.3.0': - resolution: {integrity: sha512-Fs8rtmxliPlDu/6iJniU4FfvUfD63OSxIAbMv2CyNvK9fg7dmQ9fUEBuK3WpwAlpKwhEHCzaFSWNUaB2SmeviQ==} + '@solana/keys@8.4.0': + resolution: {integrity: sha512-arjjFlJLBnGq0PWWswqO1n8kHxW02EbP9KTCoZo/TYDcSDcPdC4gL4fjAxbP6t5fVXTIu1tM3QGmab6FZxnmJg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1720,8 +1724,8 @@ packages: typescript: optional: true - '@solana/kit@8.3.0': - resolution: {integrity: sha512-2T2QaO+0/CeZnPMsjv/bGhBSEiBCMY6V/Qi0xhFF/+8yCH8tkzM/kgE7fX5JlrR3gZpl9Bu+C62KTqnvk/bcjw==} + '@solana/kit@8.4.0': + resolution: {integrity: sha512-jbexijXuCqQ7MV5lFzPDEsVScreFENkhUOUVofniTkBsfi67cYGgEeGzJa7oskHGyXcWaEogW0VPVh4ep3cWnw==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1738,8 +1742,8 @@ packages: typescript: optional: true - '@solana/nominal-types@8.3.0': - resolution: {integrity: sha512-x2aCcMwe6C2tQNq4pAhx4bEd2zhrm3i7DjkdAJAPVCtPKd6ecO6iaV+53/RvE2ImZ0E9aVB41lPV8ZM0dMhRiw==} + '@solana/nominal-types@8.4.0': + resolution: {integrity: sha512-01LNXtH3THaR0SFXbJ9iXhaZAVBfLJoHWYC/x2GdNIontc45qMX8wFtglZe4RaT528/kre6J+f+4NGGGd4+YPA==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1756,8 +1760,8 @@ packages: typescript: optional: true - '@solana/offchain-messages@8.3.0': - resolution: {integrity: sha512-zpwwQRTX+aBF97fgZW+T+grZWqKbCMa6moDnYHsCkQg67nCeb9OrPtZxMbCT5bP+kDgMTq2URQwS1NcZOwut+Q==} + '@solana/offchain-messages@8.4.0': + resolution: {integrity: sha512-vE28aDf14XldqoYGwwRG1OvsUKVal1FTxM+O1p1bPHho0bQgUiHvqtsDvQtTZBPJpx7d4l5Xo7TKN4DVZ+VgTQ==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1774,8 +1778,8 @@ packages: typescript: optional: true - '@solana/options@8.3.0': - resolution: {integrity: sha512-+mi+EvxdjlOxt3dv7wTfzGxVFw0j8flZ8gGSXCx969JrML9EZd3+wgsKuMNb3whbvGa/N5exPZXfzsG+wNlK8A==} + '@solana/options@8.4.0': + resolution: {integrity: sha512-3fnmCT/WNWOMpD5uhyi1/aI4fwtb13MQSEqiTqsboZhHClli1WAOyPhyC5I/YeJ7wF+RMzX8UE/11KU9IMazNA==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1792,8 +1796,8 @@ packages: typescript: optional: true - '@solana/plugin-core@8.3.0': - resolution: {integrity: sha512-cU5S9QR7C1Xsn9DDRSLN7Vq8+kWy6Fw6g2lwa+9yZqdSqABhmdH5HBJG/Zl8HULz2Ady0y8qHcu8fEZsehuU0g==} + '@solana/plugin-core@8.4.0': + resolution: {integrity: sha512-mRB5iQ+ZEISGyHnFvFDUJyDmK7YbPxmsJk38SKg3ncxqcSxMEhK4hZ7kJBo2UMsyDX1Sn2VSGJYPhZwdOa3s8w==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1810,8 +1814,8 @@ packages: typescript: optional: true - '@solana/plugin-interfaces@8.3.0': - resolution: {integrity: sha512-EawuCQiW5A13qdArRODYbAsAvi9fRkORk1hq14kbRAR0A/7XwQLniAWrgKY4WZ1UXuECB/LxKc8uQwfH5u1JOw==} + '@solana/plugin-interfaces@8.4.0': + resolution: {integrity: sha512-R1y8z9aBCHXnx7Aqax4ORBaC1wQaHS+KQjnbNEbVZijeJhfoKbSBA5vwlweEZPKT+l2iPL9jlxPrDlVDl4NyhQ==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1828,8 +1832,8 @@ packages: typescript: optional: true - '@solana/program-client-core@8.3.0': - resolution: {integrity: sha512-GUFVAFaEgVvznI6mrfa69I0SVnO5mD5RpYKdSEHvGnIMzupgjZhTcmhSxMIU06hFA2ZLF+NbKsOKMQFVicOCjw==} + '@solana/program-client-core@8.4.0': + resolution: {integrity: sha512-GBgz9wHMp2BOKRTp/IAATooTLPIe+6Y0jCTYOSkXME9F7xyh1o7mGu0lEbotnB6C3fubaXzU6Swk9SYskl7Sig==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1846,8 +1850,8 @@ packages: typescript: optional: true - '@solana/programs@8.3.0': - resolution: {integrity: sha512-XosDh9GNaDUCvzpFE2e3M7iQ6tounwrhR16+KInjYNcvQDLgA5yeCbNfByai7OSz9iqEi5nlXjQFIQCckLANVw==} + '@solana/programs@8.4.0': + resolution: {integrity: sha512-KZvtGP7spytak0igTSb1tulNc5fhCEyjYNlMBObveohVhrzT7/t7M6ioqcJpAvv1bWm+E7E4FI5B+fjMKMpAxQ==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1864,8 +1868,8 @@ packages: typescript: optional: true - '@solana/promises@8.3.0': - resolution: {integrity: sha512-RekTh1UzvyD1EtF4hjenJDrnpGakJwTCE7cq6auQKtBxQUIJrKwDxmpyiJTJ00wvQYdMfMuyJEPQNuxgm6mxrg==} + '@solana/promises@8.4.0': + resolution: {integrity: sha512-keb+TXJZHRT605HLakRanfjnSVUFYG6za+P7W8UDKA2ZUp+LTi1As5fQTkhRyFO/GOrbmP9a7lYC5eNGAekg6w==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1882,8 +1886,8 @@ packages: typescript: optional: true - '@solana/rpc-api@8.3.0': - resolution: {integrity: sha512-8wvVbQLbp8Mjd0Vcg2IZqkNreSGkgOPe4IhCrO2GqJQyTNAI8Ir6o/Qt3zIj02IQS9LDL9rZPvynL+1PFPSlOA==} + '@solana/rpc-api@8.4.0': + resolution: {integrity: sha512-tD26U9U7pOQoLiTp7MoyWYPSmnvLAa9OrpQPGQ8NVUU7xXOeVKSKRe3rI4EL/zzcPNXeCAKtLJ449u8ejLT7LA==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1900,8 +1904,8 @@ packages: typescript: optional: true - '@solana/rpc-parsed-types@8.3.0': - resolution: {integrity: sha512-cK0Yu7I8HADKYQdlyIuCFd2sq1sCrIm7q0H9nqeS7b5ILLYoH9T3AnCZ437N7Z4jZBekL2DGdpdE2M84F0fphQ==} + '@solana/rpc-parsed-types@8.4.0': + resolution: {integrity: sha512-/vBcGcLy/W1r8t0ST+gc3EqVPFx7dRFxkt5RoYE9tepEiroOwLbVg7rKtgtKifkFBODSVDcupEkTBBhRbWpB9A==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1918,8 +1922,8 @@ packages: typescript: optional: true - '@solana/rpc-spec-types@8.3.0': - resolution: {integrity: sha512-D757241XGKwmBRlQ1xSGknBqRaXq8ZO0BDl5UupG0UyHsfdk6iKDrJFccaNx1m2LKExzlu8iWZTEdnrueDDL7A==} + '@solana/rpc-spec-types@8.4.0': + resolution: {integrity: sha512-YnxoUzWy/bwdclB3ipu7+4/mnnb4Jqmgq1O20zhQ81cUGZseEWy++Y4GJA0OBZvrcDHhoRntSd26ABg9q0XaVg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1936,8 +1940,8 @@ packages: typescript: optional: true - '@solana/rpc-spec@8.3.0': - resolution: {integrity: sha512-HWMQsAUsW8LOkojFopkkkTSIFAFeYDA6B4NiXtXZ8q5qKRKDB+qCVTC1un8RorKrnayLM7gF73Oi5gPD2ANL/w==} + '@solana/rpc-spec@8.4.0': + resolution: {integrity: sha512-g5R8jJoYDjwb4sEUw8PxReAw5OETSjWSgMF+f/Q+0BGRiX+hNO//3qxWRL0rpWAnHi9R03W5Li/sPOYC8IGL1Q==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1954,8 +1958,8 @@ packages: typescript: optional: true - '@solana/rpc-subscriptions-api@8.3.0': - resolution: {integrity: sha512-NHjPuylayZHuBNXPkMT0sMHGUKAenck+P9Iep1ZzfH0py9SJcKOU8LHecSdGl7GjHk0SCs+iCBew+89JYrcTaA==} + '@solana/rpc-subscriptions-api@8.4.0': + resolution: {integrity: sha512-qsSYyfmYSB8/VE6yks4Ls4fQyyi2VhS09PZaENfTSFk6iKAYyEnhOkwCu//MdeWOrrHDmGmxC2UdzpB61f+Fcg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1972,8 +1976,8 @@ packages: typescript: optional: true - '@solana/rpc-subscriptions-channel-websocket@8.3.0': - resolution: {integrity: sha512-4Ix52idqRT3q0Ty5sr9lUj/S8Flf+/8q7NMuAxYNH827C71AO6vVpkpAY1YpU53MKnKy0Tox0mLLScocftgEJQ==} + '@solana/rpc-subscriptions-channel-websocket@8.4.0': + resolution: {integrity: sha512-O0UAs9G/lgBRf4Aw7kvG5quHWDWdfuTn8BJUy3KOvCFADOOGrrH6leXLHJAMxN2T8KmyO28cs2+GGW/CD5kWNQ==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -1990,8 +1994,8 @@ packages: typescript: optional: true - '@solana/rpc-subscriptions-spec@8.3.0': - resolution: {integrity: sha512-iv6f4N7Pva3KGljkT4kEBsMDjWVHShtysuPOko8doLBSKuelOj+b+79iYpezeu6hpbZ0YxBvlmJkOtUyeIhRJA==} + '@solana/rpc-subscriptions-spec@8.4.0': + resolution: {integrity: sha512-H7Jqleo+WR1hUcPexQCn5rIgGsXa7Ip0Pnl3Vzvv/DIpYEN5lpMrWXf3v9fVyG3jRxom5WW32xmYhiHa2dORzw==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2008,8 +2012,8 @@ packages: typescript: optional: true - '@solana/rpc-subscriptions@8.3.0': - resolution: {integrity: sha512-/oI9aXKOTFnIoZ+wmvZeNfSu5h8Vl90a4W6U/K8OZE2AKjK1dbKI4bR1k7P/3SORY9Z0AN2S9UsohQ6Z6n9jKA==} + '@solana/rpc-subscriptions@8.4.0': + resolution: {integrity: sha512-XkWl2flaGKkHzw6+StjVsYtJoB5xoGpnsdanXa1h1KnwK4HFFy/wsJ0P+5TY1Z0DR1IDn6voNl3KSQrQuEQm9Q==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2026,8 +2030,8 @@ packages: typescript: optional: true - '@solana/rpc-transformers@8.3.0': - resolution: {integrity: sha512-eDn1PwS+MbMAJN4ZSWzMYGt0jEwvyaZJwDeQdOdWYEkqKJKlLsO1AAxLmz3LW3R/iKyWa4KwjrJ5Ia9oAugS6g==} + '@solana/rpc-transformers@8.4.0': + resolution: {integrity: sha512-FTlLgsjN/1RKeBidS5Dfb81VpeqRYESiKg7jTkbY6MXVL96bOMUihQDXAjmgUMMp95nTykuQjKas7pkG2Zsz5w==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2044,8 +2048,8 @@ packages: typescript: optional: true - '@solana/rpc-transport-http@8.3.0': - resolution: {integrity: sha512-XD3Bo5KZekQhrReoF6UzFW66NUYqa4Rg1wBjFEIsC/sABhLJ+062O1GPS3HY9AcxZBW48rAmfNe1Qqkerxajzw==} + '@solana/rpc-transport-http@8.4.0': + resolution: {integrity: sha512-MSlFqehp1CpADeSozzb/YnBEvkBlIrTcmLIVmeKbcTMOELV5yZKnp+bMQl05Q0swsyTr423kQ1XvV9Oqk3/KoA==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2062,8 +2066,8 @@ packages: typescript: optional: true - '@solana/rpc-types@8.3.0': - resolution: {integrity: sha512-T8DlDU5hu/pspKNWriwkJHXqzb7B6EQVVC6sgciZp78VB9R413+K6QhC7jZc96mNcoaATKRRg2tDDyiezKZVIg==} + '@solana/rpc-types@8.4.0': + resolution: {integrity: sha512-orCi24z9u7AdaozJfgtXwdwHErQy5vO3h6/wCBd/W0bWsfRXiCCWqJcZJqyoZvl1y0oESGEkZR0CzrpedyaAqg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2080,8 +2084,8 @@ packages: typescript: optional: true - '@solana/rpc@8.3.0': - resolution: {integrity: sha512-giS/v09dby/VD77uDfNrhr5fLi5K5NIOp3jIFfKpdAnfPHgKLv050xdmL9ZltrWucrGuePinzUTLWkdkf4s/cw==} + '@solana/rpc@8.4.0': + resolution: {integrity: sha512-xrJa4Bapskl1aud+qirKFbDy1A22vWHGwW9YKav20Q1YjJRItE8MfJ9N4c3MAtaSkC59QhBeHOsuf3F0gm+Hxw==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2098,8 +2102,8 @@ packages: typescript: optional: true - '@solana/signers@8.3.0': - resolution: {integrity: sha512-uNiJZV6npLBQ0jYsK5fmtemZ0igQ4HW9QR9t5qSxbxRa52yZjzX7zkSpFhQJsxvBCAUR2ppVmhanDeupPXf4Mw==} + '@solana/signers@8.4.0': + resolution: {integrity: sha512-hqBzc1M/+y+6rUy7OVAisjoS0/pwhIB3B0VOoJfQ9yBjFmx8TguxcKevn4dP0DIeBK4HqQgiqJyJN0f5hbHfxQ==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2116,8 +2120,8 @@ packages: typescript: optional: true - '@solana/subscribable@8.3.0': - resolution: {integrity: sha512-tLoGNOt19oUTxIoOaBxQRNxKJET/IXBx1NZkGD1lh4X8iNc6V0lY3MMIWjNvRivFIpzeWtWYxX/tAb27xpmcXg==} + '@solana/subscribable@8.4.0': + resolution: {integrity: sha512-EgRMZWnxo1GYan014TscE5i9iDl/dgazwRAgyQ/QRUtB5FVcedRHr0Wh6dW6MJno4htKN4rZsMN+jAuaxLNfmw==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2134,8 +2138,8 @@ packages: typescript: optional: true - '@solana/sysvars@8.3.0': - resolution: {integrity: sha512-SXnKqpV4pj3vLvcyBn8+jeVouu4Bbk0PTmHMknsig6edWcpmvAXkufcRdxgVy5t58pdSf1+GlJGEEhP5NR/0xA==} + '@solana/sysvars@8.4.0': + resolution: {integrity: sha512-M2Wk70cji0n0muwHuZiPpDLeRy3Iq19FSOCNQPC/VL5Bg4Ltm7G9PXPvX4/TEODfFGgOLGja4rZmFlECl7tlXg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2152,8 +2156,8 @@ packages: typescript: optional: true - '@solana/transaction-confirmation@8.3.0': - resolution: {integrity: sha512-xx4sO4+0nP84SvxPPmHFKkUyBEQDFC2Vn8eqV8qEG3yo0ruHlZ45g0IeYhz9WS20WPgkXvGB5bVU8Y3yv/4vPw==} + '@solana/transaction-confirmation@8.4.0': + resolution: {integrity: sha512-tTKRbzqUzpQGh4ZmL8MTyvZBWHzvcuvDrGROFk8vw2b1FINsv6BAKbxXKWUTWp2RO0ABcznMt+sFHAyPwQ/2/w==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2161,8 +2165,8 @@ packages: typescript: optional: true - '@solana/transaction-introspection@8.3.0': - resolution: {integrity: sha512-uuPNYLhgueMG1ZxWH+SqPHiyz7KM0vFUDPCVRQUVTTWFryWDeu6GExfy+mFMPRR25cp+hWYmYhddub8BU2sPXQ==} + '@solana/transaction-introspection@8.4.0': + resolution: {integrity: sha512-qVvPIRh7Q76k1UNcy2wZIKpA6emmfhMVNUFNheD+eCzOycGDcCT3Y/Mt7H5qU+dDq4NKLNR/20h4aRu1Qendbg==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2179,8 +2183,8 @@ packages: typescript: optional: true - '@solana/transaction-messages@8.3.0': - resolution: {integrity: sha512-AkgHRPOnwonB7n81rTwnZ9WFlagnhzDzXKGhETdffzC+A1GifXC2s/ou0/qNozuqlgPVDaBR02S/A3Vt1tYr/g==} + '@solana/transaction-messages@8.4.0': + resolution: {integrity: sha512-LdihUr4z6bAIv44z13uaShCXgb9C8DY0gZ+RcW9U+CcaDJwWhgZuy5IG2TBAAGa5lbEyRcYPQxD+/aE6b1F/WA==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -2197,8 +2201,8 @@ packages: typescript: optional: true - '@solana/transactions@8.3.0': - resolution: {integrity: sha512-zNCHu2W3t6r4W3rsCA5vZ93bGqcZPNgfgYjTamRC6xTZPP2qn5JR+bSR9/WBMpi8GwLzWYuDk6YtNp3+pYGKuQ==} + '@solana/transactions@8.4.0': + resolution: {integrity: sha512-+FFNSlr5vVTcGS9c7seH7mNnjZY21uZ3pHxiN9jeduODD7z+caKnGgjmzokZSDYapFUcupniIr736CrJNmbMFw==} engines: {node: '>=20.18.0'} peerDependencies: typescript: '>=5.4.0' @@ -3140,6 +3144,9 @@ packages: undici-types@8.10.2: resolution: {integrity: sha512-7/+aSjzkUoLc92hV22bTW4aGanXf800zbwguhcICs0OAoCF9wDOE4wkopQ+SqfhXZm8mCK8gHpdTs7pZUWzK3w==} + undici-types@8.11.2: + resolution: {integrity: sha512-iMVNmWZ0leK/goS6eXMizSzmm9CDWtyphwbaCms3DNLqRxDL+mMoNVcZMTyyVgXP0N+Z8neAMzDoUOUJL8veKg==} + undici-types@8.9.0: resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} @@ -3904,47 +3911,47 @@ snapshots: oxfmt: 0.67.0 oxlint: 1.82.0(oxlint-tsgolint@7.0.2001) - '@solana-program/program-metadata@0.9.3(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': + '@solana-program/program-metadata@0.9.3(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': dependencies: - '@solana-program/system': 0.14.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) - '@solana/kit': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana-program/system': 0.14.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + '@solana/kit': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) commander: 13.1.0 pako: 2.2.0 picocolors: 1.1.1 smol-toml: 1.8.0 yaml: 2.9.0 - '@solana-program/record@0.4.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': + '@solana-program/record@0.4.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': dependencies: - '@solana-program/system': 0.14.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) - '@solana/kit': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana-program/system': 0.14.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + '@solana/kit': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) - '@solana-program/system@0.14.0(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': + '@solana-program/system@0.14.0(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': dependencies: - '@solana/kit': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana/kit': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) - '@solana-program/system@0.14.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': + '@solana-program/system@0.14.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': dependencies: - '@solana/kit': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana/kit': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) - '@solana-program/token-2022@0.17.0(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))(@solana/sysvars@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))': + '@solana-program/token-2022@0.17.0(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))(@solana/sysvars@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2))': dependencies: '@noble/curves': 2.4.0 - '@solana-program/record': 0.4.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) - '@solana-program/system': 0.14.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) - '@solana-program/zk-elgamal-proof': 0.4.0(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) - '@solana/kit': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) - '@solana/sysvars': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana-program/record': 0.4.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + '@solana-program/system': 0.14.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + '@solana-program/zk-elgamal-proof': 0.4.0(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + '@solana/kit': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana/sysvars': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana-program/token@0.16.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': + '@solana-program/token@0.16.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': dependencies: - '@solana-program/system': 0.14.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) - '@solana/kit': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana-program/system': 0.14.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + '@solana/kit': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) - '@solana-program/zk-elgamal-proof@0.4.0(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': + '@solana-program/zk-elgamal-proof@0.4.0(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6))': dependencies: - '@solana-program/system': 0.14.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) - '@solana/kit': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana-program/system': 0.14.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + '@solana/kit': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) '@solana/accounts@6.10.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: @@ -3959,14 +3966,14 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/accounts@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/accounts@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-strings': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/rpc-spec': 8.3.0(typescript@7.0.2) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-strings': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/rpc-spec': 8.4.0(typescript@7.0.2) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -3984,13 +3991,13 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/addresses@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/addresses@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/assertions': 8.3.0(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-strings': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/nominal-types': 8.3.0(typescript@7.0.2) + '@solana/assertions': 8.4.0(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-strings': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/nominal-types': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4002,9 +4009,9 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/assertions@8.3.0(typescript@7.0.2)': + '@solana/assertions@8.4.0(typescript@7.0.2)': dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -4014,9 +4021,9 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/codecs-core@8.3.0(typescript@7.0.2)': + '@solana/codecs-core@8.4.0(typescript@7.0.2)': dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -4028,11 +4035,11 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/codecs-data-structures@8.3.0(typescript@7.0.2)': + '@solana/codecs-data-structures@8.4.0(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-numbers': 8.3.0(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-numbers': 8.4.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -4043,10 +4050,10 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/codecs-numbers@8.3.0(typescript@7.0.2)': + '@solana/codecs-numbers@8.4.0(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -4059,11 +4066,11 @@ snapshots: fastestsmallesttextencoderdecoder: 1.0.22 typescript: 7.0.2 - '@solana/codecs-strings@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/codecs-strings@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-numbers': 8.3.0(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-numbers': 8.4.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) optionalDependencies: fastestsmallesttextencoderdecoder: 1.0.22 typescript: 7.0.2 @@ -4081,14 +4088,14 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/codecs@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/codecs@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-data-structures': 8.3.0(typescript@7.0.2) - '@solana/codecs-numbers': 8.3.0(typescript@7.0.2) - '@solana/codecs-strings': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/fixed-points': 8.3.0(typescript@7.0.2) - '@solana/options': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-data-structures': 8.4.0(typescript@7.0.2) + '@solana/codecs-numbers': 8.4.0(typescript@7.0.2) + '@solana/codecs-strings': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/fixed-points': 8.4.0(typescript@7.0.2) + '@solana/options': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4101,7 +4108,7 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/errors@8.3.0(typescript@7.0.2)': + '@solana/errors@8.4.0(typescript@7.0.2)': dependencies: chalk: 5.6.2 commander: 15.0.0 @@ -4112,7 +4119,7 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/fast-stable-stringify@8.3.0(typescript@7.0.2)': + '@solana/fast-stable-stringify@8.4.0(typescript@7.0.2)': optionalDependencies: typescript: 7.0.2 @@ -4123,10 +4130,10 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/fixed-points@8.3.0(typescript@7.0.2)': + '@solana/fixed-points@8.4.0(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -4134,7 +4141,7 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/functional@8.3.0(typescript@7.0.2)': + '@solana/functional@8.4.0(typescript@7.0.2)': optionalDependencies: typescript: 7.0.2 @@ -4151,14 +4158,14 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/instruction-plans@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/instruction-plans@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/instructions': 8.3.0(typescript@7.0.2) - '@solana/keys': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/promises': 8.3.0(typescript@7.0.2) - '@solana/transaction-messages': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transactions': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/instructions': 8.4.0(typescript@7.0.2) + '@solana/keys': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/promises': 8.4.0(typescript@7.0.2) + '@solana/transaction-messages': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4171,10 +4178,10 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/instructions@8.3.0(typescript@7.0.2)': + '@solana/instructions@8.4.0(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -4191,14 +4198,14 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/keys@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/keys@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/assertions': 8.3.0(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-strings': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/nominal-types': 8.3.0(typescript@7.0.2) - '@solana/promises': 8.3.0(typescript@7.0.2) + '@solana/assertions': 8.4.0(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-strings': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/nominal-types': 8.4.0(typescript@7.0.2) + '@solana/promises': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4238,35 +4245,35 @@ snapshots: - fastestsmallesttextencoderdecoder - utf-8-validate - '@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)': - dependencies: - '@solana/accounts': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/functional': 8.3.0(typescript@7.0.2) - '@solana/instruction-plans': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/instructions': 8.3.0(typescript@7.0.2) - '@solana/keys': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/offchain-messages': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/plugin-core': 8.3.0(typescript@7.0.2) - '@solana/plugin-interfaces': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/program-client-core': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/programs': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/promises': 8.3.0(typescript@7.0.2) - '@solana/rpc': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-api': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-parsed-types': 8.3.0(typescript@7.0.2) - '@solana/rpc-spec-types': 8.3.0(typescript@7.0.2) - '@solana/rpc-subscriptions': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/signers': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/subscribable': 8.3.0(typescript@7.0.2) - '@solana/sysvars': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transaction-confirmation': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) - '@solana/transaction-introspection': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transaction-messages': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transactions': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)': + dependencies: + '@solana/accounts': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/functional': 8.4.0(typescript@7.0.2) + '@solana/instruction-plans': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/instructions': 8.4.0(typescript@7.0.2) + '@solana/keys': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/offchain-messages': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/plugin-core': 8.4.0(typescript@7.0.2) + '@solana/plugin-interfaces': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/program-client-core': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/programs': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/promises': 8.4.0(typescript@7.0.2) + '@solana/rpc': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-api': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-parsed-types': 8.4.0(typescript@7.0.2) + '@solana/rpc-spec-types': 8.4.0(typescript@7.0.2) + '@solana/rpc-subscriptions': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/signers': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/subscribable': 8.4.0(typescript@7.0.2) + '@solana/sysvars': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-confirmation': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana/transaction-introspection': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4278,7 +4285,7 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/nominal-types@8.3.0(typescript@7.0.2)': + '@solana/nominal-types@8.4.0(typescript@7.0.2)': optionalDependencies: typescript: 7.0.2 @@ -4297,16 +4304,16 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/offchain-messages@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/offchain-messages@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-data-structures': 8.3.0(typescript@7.0.2) - '@solana/codecs-numbers': 8.3.0(typescript@7.0.2) - '@solana/codecs-strings': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/keys': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/nominal-types': 8.3.0(typescript@7.0.2) + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-data-structures': 8.4.0(typescript@7.0.2) + '@solana/codecs-numbers': 8.4.0(typescript@7.0.2) + '@solana/codecs-strings': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/keys': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/nominal-types': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4324,13 +4331,13 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/options@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/options@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-data-structures': 8.3.0(typescript@7.0.2) - '@solana/codecs-numbers': 8.3.0(typescript@7.0.2) - '@solana/codecs-strings': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-data-structures': 8.4.0(typescript@7.0.2) + '@solana/codecs-numbers': 8.4.0(typescript@7.0.2) + '@solana/codecs-strings': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4340,7 +4347,7 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/plugin-core@8.3.0(typescript@7.0.2)': + '@solana/plugin-core@8.4.0(typescript@7.0.2)': optionalDependencies: typescript: 7.0.2 @@ -4358,17 +4365,17 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/plugin-interfaces@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': - dependencies: - '@solana/accounts': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/instruction-plans': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/keys': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-spec': 8.3.0(typescript@7.0.2) - '@solana/rpc-subscriptions-spec': 8.3.0(typescript@7.0.2) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/signers': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transactions': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/plugin-interfaces@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/accounts': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/instruction-plans': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/keys': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-spec': 8.4.0(typescript@7.0.2) + '@solana/rpc-subscriptions-spec': 8.4.0(typescript@7.0.2) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/signers': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4390,17 +4397,17 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/program-client-core@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': - dependencies: - '@solana/accounts': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/instruction-plans': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/instructions': 8.3.0(typescript@7.0.2) - '@solana/plugin-interfaces': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-api': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/signers': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/program-client-core@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/accounts': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/instruction-plans': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/instructions': 8.4.0(typescript@7.0.2) + '@solana/plugin-interfaces': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-api': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/signers': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4415,10 +4422,10 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/programs@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/programs@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4428,7 +4435,7 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/promises@8.3.0(typescript@7.0.2)': + '@solana/promises@8.4.0(typescript@7.0.2)': optionalDependencies: typescript: 7.0.2 @@ -4450,19 +4457,19 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/rpc-api@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': - dependencies: - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-strings': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/keys': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-parsed-types': 8.3.0(typescript@7.0.2) - '@solana/rpc-spec': 8.3.0(typescript@7.0.2) - '@solana/rpc-transformers': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transaction-messages': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transactions': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-api@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-strings': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/keys': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-parsed-types': 8.4.0(typescript@7.0.2) + '@solana/rpc-spec': 8.4.0(typescript@7.0.2) + '@solana/rpc-transformers': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4472,7 +4479,7 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/rpc-parsed-types@8.3.0(typescript@7.0.2)': + '@solana/rpc-parsed-types@8.4.0(typescript@7.0.2)': optionalDependencies: typescript: 7.0.2 @@ -4480,9 +4487,9 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/rpc-spec-types@8.3.0(typescript@7.0.2)': + '@solana/rpc-spec-types@8.4.0(typescript@7.0.2)': dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -4494,11 +4501,11 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/rpc-spec@8.3.0(typescript@7.0.2)': + '@solana/rpc-spec@8.4.0(typescript@7.0.2)': dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/rpc-spec-types': 8.3.0(typescript@7.0.2) - '@solana/subscribable': 8.3.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/rpc-spec-types': 8.4.0(typescript@7.0.2) + '@solana/subscribable': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -4516,15 +4523,15 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/rpc-subscriptions-api@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/rpc-subscriptions-api@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/keys': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-subscriptions-spec': 8.3.0(typescript@7.0.2) - '@solana/rpc-transformers': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transaction-messages': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transactions': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/keys': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-subscriptions-spec': 8.4.0(typescript@7.0.2) + '@solana/rpc-transformers': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4543,12 +4550,12 @@ snapshots: - bufferutil - utf-8-validate - '@solana/rpc-subscriptions-channel-websocket@8.3.0(bufferutil@4.1.0)(typescript@7.0.2)(utf-8-validate@6.0.6)': + '@solana/rpc-subscriptions-channel-websocket@8.4.0(bufferutil@4.1.0)(typescript@7.0.2)(utf-8-validate@6.0.6)': dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/functional': 8.3.0(typescript@7.0.2) - '@solana/rpc-subscriptions-spec': 8.3.0(typescript@7.0.2) - '@solana/subscribable': 8.3.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/functional': 8.4.0(typescript@7.0.2) + '@solana/rpc-subscriptions-spec': 8.4.0(typescript@7.0.2) + '@solana/subscribable': 8.4.0(typescript@7.0.2) ws: 8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: typescript: 7.0.2 @@ -4565,12 +4572,12 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/rpc-subscriptions-spec@8.3.0(typescript@7.0.2)': + '@solana/rpc-subscriptions-spec@8.4.0(typescript@7.0.2)': dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/promises': 8.3.0(typescript@7.0.2) - '@solana/rpc-spec-types': 8.3.0(typescript@7.0.2) - '@solana/subscribable': 8.3.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/promises': 8.4.0(typescript@7.0.2) + '@solana/rpc-spec-types': 8.4.0(typescript@7.0.2) + '@solana/subscribable': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -4594,19 +4601,19 @@ snapshots: - fastestsmallesttextencoderdecoder - utf-8-validate - '@solana/rpc-subscriptions@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)': - dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/fast-stable-stringify': 8.3.0(typescript@7.0.2) - '@solana/functional': 8.3.0(typescript@7.0.2) - '@solana/promises': 8.3.0(typescript@7.0.2) - '@solana/rpc-spec-types': 8.3.0(typescript@7.0.2) - '@solana/rpc-subscriptions-api': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-subscriptions-channel-websocket': 8.3.0(bufferutil@4.1.0)(typescript@7.0.2)(utf-8-validate@6.0.6) - '@solana/rpc-subscriptions-spec': 8.3.0(typescript@7.0.2) - '@solana/rpc-transformers': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/subscribable': 8.3.0(typescript@7.0.2) + '@solana/rpc-subscriptions@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)': + dependencies: + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/fast-stable-stringify': 8.4.0(typescript@7.0.2) + '@solana/functional': 8.4.0(typescript@7.0.2) + '@solana/promises': 8.4.0(typescript@7.0.2) + '@solana/rpc-spec-types': 8.4.0(typescript@7.0.2) + '@solana/rpc-subscriptions-api': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-subscriptions-channel-websocket': 8.4.0(bufferutil@4.1.0)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana/rpc-subscriptions-spec': 8.4.0(typescript@7.0.2) + '@solana/rpc-transformers': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/subscribable': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4626,13 +4633,13 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/rpc-transformers@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/rpc-transformers@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/functional': 8.3.0(typescript@7.0.2) - '@solana/nominal-types': 8.3.0(typescript@7.0.2) - '@solana/rpc-spec-types': 8.3.0(typescript@7.0.2) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/functional': 8.4.0(typescript@7.0.2) + '@solana/nominal-types': 8.4.0(typescript@7.0.2) + '@solana/rpc-spec-types': 8.4.0(typescript@7.0.2) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4647,12 +4654,12 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/rpc-transport-http@8.3.0(typescript@7.0.2)': + '@solana/rpc-transport-http@8.4.0(typescript@7.0.2)': dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/rpc-spec': 8.3.0(typescript@7.0.2) - '@solana/rpc-spec-types': 8.3.0(typescript@7.0.2) - undici-types: 8.10.2 + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/rpc-spec': 8.4.0(typescript@7.0.2) + '@solana/rpc-spec-types': 8.4.0(typescript@7.0.2) + undici-types: 8.11.2 optionalDependencies: typescript: 7.0.2 @@ -4670,15 +4677,15 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/rpc-types@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/rpc-types@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-numbers': 8.3.0(typescript@7.0.2) - '@solana/codecs-strings': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/fixed-points': 8.3.0(typescript@7.0.2) - '@solana/nominal-types': 8.3.0(typescript@7.0.2) + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-numbers': 8.4.0(typescript@7.0.2) + '@solana/codecs-strings': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/fixed-points': 8.4.0(typescript@7.0.2) + '@solana/nominal-types': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4700,17 +4707,17 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/rpc@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': - dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/fast-stable-stringify': 8.3.0(typescript@7.0.2) - '@solana/functional': 8.3.0(typescript@7.0.2) - '@solana/rpc-api': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-spec': 8.3.0(typescript@7.0.2) - '@solana/rpc-spec-types': 8.3.0(typescript@7.0.2) - '@solana/rpc-transformers': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-transport-http': 8.3.0(typescript@7.0.2) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/fast-stable-stringify': 8.4.0(typescript@7.0.2) + '@solana/functional': 8.4.0(typescript@7.0.2) + '@solana/rpc-api': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-spec': 8.4.0(typescript@7.0.2) + '@solana/rpc-spec-types': 8.4.0(typescript@7.0.2) + '@solana/rpc-transformers': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-transport-http': 8.4.0(typescript@7.0.2) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4732,17 +4739,17 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/signers@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': - dependencies: - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/instructions': 8.3.0(typescript@7.0.2) - '@solana/keys': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/nominal-types': 8.3.0(typescript@7.0.2) - '@solana/offchain-messages': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transaction-messages': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transactions': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/signers@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/instructions': 8.4.0(typescript@7.0.2) + '@solana/keys': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/nominal-types': 8.4.0(typescript@7.0.2) + '@solana/offchain-messages': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4755,10 +4762,10 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@solana/subscribable@8.3.0(typescript@7.0.2)': + '@solana/subscribable@8.4.0(typescript@7.0.2)': dependencies: - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/promises': 8.3.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/promises': 8.4.0(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 @@ -4775,14 +4782,14 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/sysvars@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/sysvars@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/accounts': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-data-structures': 8.3.0(typescript@7.0.2) - '@solana/codecs-numbers': 8.3.0(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/accounts': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-data-structures': 8.4.0(typescript@7.0.2) + '@solana/codecs-numbers': 8.4.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4807,18 +4814,18 @@ snapshots: - fastestsmallesttextencoderdecoder - utf-8-validate - '@solana/transaction-confirmation@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)': - dependencies: - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs-strings': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/keys': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/promises': 8.3.0(typescript@7.0.2) - '@solana/rpc': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/rpc-subscriptions': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transaction-messages': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transactions': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-confirmation@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)': + dependencies: + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-strings': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/keys': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/promises': 8.4.0(typescript@7.0.2) + '@solana/rpc': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/rpc-subscriptions': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4826,16 +4833,16 @@ snapshots: - fastestsmallesttextencoderdecoder - utf-8-validate - '@solana/transaction-introspection@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + '@solana/transaction-introspection@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': dependencies: - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-strings': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/instructions': 8.3.0(typescript@7.0.2) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transaction-messages': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transactions': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-strings': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/instructions': 8.4.0(typescript@7.0.2) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4857,17 +4864,17 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/transaction-messages@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': - dependencies: - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-data-structures': 8.3.0(typescript@7.0.2) - '@solana/codecs-numbers': 8.3.0(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/functional': 8.3.0(typescript@7.0.2) - '@solana/instructions': 8.3.0(typescript@7.0.2) - '@solana/nominal-types': 8.3.0(typescript@7.0.2) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-data-structures': 8.4.0(typescript@7.0.2) + '@solana/codecs-numbers': 8.4.0(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/functional': 8.4.0(typescript@7.0.2) + '@solana/instructions': 8.4.0(typescript@7.0.2) + '@solana/nominal-types': 8.4.0(typescript@7.0.2) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4892,20 +4899,20 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/transactions@8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': - dependencies: - '@solana/addresses': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/codecs-core': 8.3.0(typescript@7.0.2) - '@solana/codecs-data-structures': 8.3.0(typescript@7.0.2) - '@solana/codecs-numbers': 8.3.0(typescript@7.0.2) - '@solana/codecs-strings': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/errors': 8.3.0(typescript@7.0.2) - '@solana/functional': 8.3.0(typescript@7.0.2) - '@solana/instructions': 8.3.0(typescript@7.0.2) - '@solana/keys': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/nominal-types': 8.3.0(typescript@7.0.2) - '@solana/rpc-types': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) - '@solana/transaction-messages': 8.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transactions@8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)': + dependencies: + '@solana/addresses': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/codecs-core': 8.4.0(typescript@7.0.2) + '@solana/codecs-data-structures': 8.4.0(typescript@7.0.2) + '@solana/codecs-numbers': 8.4.0(typescript@7.0.2) + '@solana/codecs-strings': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/errors': 8.4.0(typescript@7.0.2) + '@solana/functional': 8.4.0(typescript@7.0.2) + '@solana/instructions': 8.4.0(typescript@7.0.2) + '@solana/keys': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/nominal-types': 8.4.0(typescript@7.0.2) + '@solana/rpc-types': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) + '@solana/transaction-messages': 8.4.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -5429,9 +5436,9 @@ snapshots: litesvm@1.4.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6): dependencies: - '@solana-program/system': 0.14.0(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) - '@solana-program/token': 0.16.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) - '@solana/kit': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana-program/system': 0.14.0(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + '@solana-program/token': 0.16.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)) + '@solana/kit': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) optionalDependencies: litesvm-darwin-arm64: 1.4.1 litesvm-darwin-x64: 1.4.1 @@ -5680,9 +5687,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.63.3 fsevents: 2.3.3 - sas-lib@2.0.0-beta.1(@solana/kit@8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)): + sas-lib@2.0.0-beta.1(@solana/kit@8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6)): dependencies: - '@solana/kit': 8.3.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@solana/kit': 8.4.0(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@7.0.2)(utf-8-validate@6.0.6) semver@7.8.5: {} @@ -5849,6 +5856,8 @@ snapshots: undici-types@8.10.2: {} + undici-types@8.11.2: {} + undici-types@8.9.0: {} update-browserslist-db@1.3.3(browserslist@4.25.4):