diff --git a/CHANGELOG.md b/CHANGELOG.md
index 622d06b75..6b96be7e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,17 +12,18 @@ The minor version will be incremented upon a breaking change and the patch versi
### Features
+* lang: Add `PartialEq` and `Eq` for `anchor_lang::Error` ([#1544](https://github.com/project-serum/anchor/pull/1544)).
* cli: Add `--skip-build` to `anchor publish` ([#1786](https://github.
com/project-serum/anchor/pull/1841)).
* cli: Add `--program-keypair` to `anchor deploy` ([#1786](https://github.com/project-serum/anchor/pull/1786)).
-* spl: Add more derived traits to `TokenAccount` to `Mint` ([#1818](https://github.com/project-serum/anchor/pull/1818)).
* cli: Add compilation optimizations to cli template ([#1807](https://github.com/project-serum/anchor/pull/1807)).
* cli: `build` now adds docs to idl. This can be turned off with `--no-docs` ([#1561](https://github.com/project-serum/anchor/pull/1561)).
-* lang: Add `PartialEq` and `Eq` for `anchor_lang::Error` ([#1544](https://github.com/project-serum/anchor/pull/1544)).
* cli: Add `b` and `t` aliases for `build` and `test` respectively ([#1823](https://github.com/project-serum/anchor/pull/1823)).
+* spl: Add more derived traits to `TokenAccount` to `Mint` ([#1818](https://github.com/project-serum/anchor/pull/1818)).
* spl: Add `sync_native` token program CPI wrapper function ([#1833](https://github.com/project-serum/anchor/pull/1833)).
* ts: Implement a coder for system program ([#1920](https://github.com/project-serum/anchor/pull/1920)).
-* client: Add send_with_spinner_and_config function to RequestBuilder ([#1926](https://github.com/project-serum/anchor/pull/1926))
+* ts: Add `program.coder.types` for encoding/decoding user-defined types ([#1931](https://github.com/project-serum/anchor/pull/1931)).
+* client: Add send_with_spinner_and_config function to RequestBuilder ([#1926](https://github.com/project-serum/anchor/pull/1926)).
### Fixes
diff --git a/ts/src/coder/borsh/index.ts b/ts/src/coder/borsh/index.ts
index 4b84fd2ed..708ff2c03 100644
--- a/ts/src/coder/borsh/index.ts
+++ b/ts/src/coder/borsh/index.ts
@@ -3,6 +3,7 @@ import { BorshInstructionCoder } from "./instruction.js";
import { BorshAccountsCoder } from "./accounts.js";
import { BorshEventCoder } from "./event.js";
import { BorshStateCoder } from "./state.js";
+import { BorshTypesCoder } from "./types.js";
import { Coder } from "../index.js";
export { BorshInstructionCoder } from "./instruction.js";
@@ -14,7 +15,9 @@ export { BorshStateCoder, stateDiscriminator } from "./state.js";
* BorshCoder is the default Coder for Anchor programs implementing the
* borsh based serialization interface.
*/
-export class BorshCoder implements Coder {
+export class BorshCoder
+ implements Coder
+{
/**
* Instruction coder.
*/
@@ -35,6 +38,11 @@ export class BorshCoder implements Coder {
*/
readonly events: BorshEventCoder;
+ /**
+ * Coder for user-defined types.
+ */
+ readonly types: BorshTypesCoder;
+
constructor(idl: Idl) {
this.instruction = new BorshInstructionCoder(idl);
this.accounts = new BorshAccountsCoder(idl);
@@ -42,5 +50,6 @@ export class BorshCoder implements Coder {
if (idl.state) {
this.state = new BorshStateCoder(idl);
}
+ this.types = new BorshTypesCoder(idl);
}
}
diff --git a/ts/src/coder/borsh/types.ts b/ts/src/coder/borsh/types.ts
new file mode 100644
index 000000000..848e4faa3
--- /dev/null
+++ b/ts/src/coder/borsh/types.ts
@@ -0,0 +1,52 @@
+import { Buffer } from "buffer";
+import { Layout } from "buffer-layout";
+import { Idl } from "../../idl.js";
+import { IdlCoder } from "./idl.js";
+import { TypesCoder } from "../index.js";
+
+/**
+ * Encodes and decodes user-defined types.
+ */
+export class BorshTypesCoder implements TypesCoder {
+ /**
+ * Maps type name to a layout.
+ */
+ private typeLayouts: Map;
+
+ /**
+ * IDL whose types will be coded.
+ */
+ private idl: Idl;
+
+ public constructor(idl: Idl) {
+ if (idl.types === undefined) {
+ this.typeLayouts = new Map();
+ return;
+ }
+ const layouts: [N, Layout][] = idl.types.map((acc) => {
+ return [acc.name as N, IdlCoder.typeDefLayout(acc, idl.types)];
+ });
+
+ this.typeLayouts = new Map(layouts);
+ this.idl = idl;
+ }
+
+ public encode(typeName: N, type: T): Buffer {
+ const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
+ const layout = this.typeLayouts.get(typeName);
+ if (!layout) {
+ throw new Error(`Unknown type: ${typeName}`);
+ }
+ const len = layout.encode(type, buffer);
+
+ return buffer.slice(0, len);
+ }
+
+ public decode(typeName: N, typeData: Buffer): T {
+ const layout = this.typeLayouts.get(typeName);
+ if (!layout) {
+ throw new Error(`Unknown type: ${typeName}`);
+ }
+ return layout.decode(typeData);
+ }
+}
diff --git a/ts/src/coder/index.ts b/ts/src/coder/index.ts
index 29a15ce16..8dc630a6f 100644
--- a/ts/src/coder/index.ts
+++ b/ts/src/coder/index.ts
@@ -8,7 +8,7 @@ export * from "./system/index.js";
/**
* Coder provides a facade for encoding and decoding all IDL related objects.
*/
-export interface Coder {
+export interface Coder {
/**
* Instruction coder.
*/
@@ -17,7 +17,7 @@ export interface Coder {
/**
* Account coder.
*/
- readonly accounts: AccountsCoder;
+ readonly accounts: AccountsCoder;
/**
* Coder for state structs.
@@ -28,6 +28,11 @@ export interface Coder {
* Coder for events.
*/
readonly events: EventCoder;
+
+ /**
+ * Coder for user-defined types.
+ */
+ readonly types: TypesCoder;
}
export interface StateCoder {
@@ -53,3 +58,8 @@ export interface EventCoder {
log: string
): Event | null;
}
+
+export interface TypesCoder {
+ encode(typeName: N, type: T): Buffer;
+ decode(typeName: N, typeData: Buffer): T;
+}
diff --git a/ts/src/coder/spl-token/index.ts b/ts/src/coder/spl-token/index.ts
index 188b32b28..f7d33e798 100644
--- a/ts/src/coder/spl-token/index.ts
+++ b/ts/src/coder/spl-token/index.ts
@@ -4,6 +4,7 @@ import { SplTokenInstructionCoder } from "./instruction.js";
import { SplTokenStateCoder } from "./state.js";
import { SplTokenAccountsCoder } from "./accounts.js";
import { SplTokenEventsCoder } from "./events.js";
+import { SplTokenTypesCoder } from "./types.js";
/**
* Coder for the SPL token program.
@@ -13,11 +14,13 @@ export class SplTokenCoder implements Coder {
readonly accounts: SplTokenAccountsCoder;
readonly state: SplTokenStateCoder;
readonly events: SplTokenEventsCoder;
+ readonly types: SplTokenTypesCoder;
constructor(idl: Idl) {
this.instruction = new SplTokenInstructionCoder(idl);
this.accounts = new SplTokenAccountsCoder(idl);
this.events = new SplTokenEventsCoder(idl);
this.state = new SplTokenStateCoder(idl);
+ this.types = new SplTokenTypesCoder(idl);
}
}
diff --git a/ts/src/coder/spl-token/types.ts b/ts/src/coder/spl-token/types.ts
new file mode 100644
index 000000000..fee1e00de
--- /dev/null
+++ b/ts/src/coder/spl-token/types.ts
@@ -0,0 +1,13 @@
+import { TypesCoder } from "../index.js";
+import { Idl } from "../../idl.js";
+
+export class SplTokenTypesCoder implements TypesCoder {
+ constructor(_idl: Idl) {}
+
+ encode(_name: string, _type: T): Buffer {
+ throw new Error("SPL token does not have user-defined types");
+ }
+ decode(_name: string, _typeData: Buffer): T {
+ throw new Error("SPL token does not have user-defined types");
+ }
+}
diff --git a/ts/src/coder/system/index.ts b/ts/src/coder/system/index.ts
index be8db86ad..f4efa131c 100644
--- a/ts/src/coder/system/index.ts
+++ b/ts/src/coder/system/index.ts
@@ -4,6 +4,7 @@ import { SystemInstructionCoder } from "./instruction.js";
import { SystemStateCoder } from "./state.js";
import { SystemAccountsCoder } from "./accounts.js";
import { SystemEventsCoder } from "./events.js";
+import { SystemTypesCoder } from "./types.js";
/**
* Coder for the System program.
@@ -13,11 +14,13 @@ export class SystemCoder implements Coder {
readonly accounts: SystemAccountsCoder;
readonly state: SystemStateCoder;
readonly events: SystemEventsCoder;
+ readonly types: SystemTypesCoder;
constructor(idl: Idl) {
this.instruction = new SystemInstructionCoder(idl);
this.accounts = new SystemAccountsCoder(idl);
this.events = new SystemEventsCoder(idl);
this.state = new SystemStateCoder(idl);
+ this.types = new SystemTypesCoder(idl);
}
}
diff --git a/ts/src/coder/system/types.ts b/ts/src/coder/system/types.ts
new file mode 100644
index 000000000..65d63b2ad
--- /dev/null
+++ b/ts/src/coder/system/types.ts
@@ -0,0 +1,13 @@
+import { TypesCoder } from "../index.js";
+import { Idl } from "../../idl.js";
+
+export class SystemTypesCoder implements TypesCoder {
+ constructor(_idl: Idl) {}
+
+ encode(_name: string, _type: T): Buffer {
+ throw new Error("System does not have user-defined types");
+ }
+ decode(_name: string, _typeData: Buffer): T {
+ throw new Error("System does not have user-defined types");
+ }
+}
diff --git a/ts/tests/coder-types.spec.ts b/ts/tests/coder-types.spec.ts
new file mode 100644
index 000000000..4e2b93c8f
--- /dev/null
+++ b/ts/tests/coder-types.spec.ts
@@ -0,0 +1,45 @@
+import * as assert from "assert";
+import { BorshCoder } from "../src";
+
+describe("coder.types", () => {
+ test("Can encode and decode user-defined types", () => {
+ const idl = {
+ version: "0.0.0",
+ name: "basic_0",
+ instructions: [
+ {
+ name: "initialize",
+ accounts: [],
+ args: [],
+ },
+ ],
+ types: [
+ {
+ name: "MintInfo",
+ type: {
+ kind: "struct" as const,
+ fields: [
+ {
+ name: "minted",
+ type: "bool" as const,
+ },
+ {
+ name: "metadataUrl",
+ type: "string" as const,
+ },
+ ],
+ },
+ },
+ ],
+ };
+ const coder = new BorshCoder(idl);
+
+ const mintInfo = {
+ minted: true,
+ metadataUrl: "hello",
+ };
+ const encoded = coder.types.encode("MintInfo", mintInfo);
+
+ assert.deepEqual(coder.types.decode("MintInfo", encoded), mintInfo);
+ });
+});