浏览代码

feat: "base64 bytes to X" functions (#301)

* feat: base64 bytes

* fix: kit and buffer
Nick Frostbutter 1 月之前
父节点
当前提交
54e2e96f87

+ 5 - 0
.changeset/wet-boats-talk.md

@@ -0,0 +1,5 @@
+---
+"gill": minor
+---
+
+added "base64 bytes to X" functions

+ 135 - 0
packages/gill/src/__tests__/base64-bytes-to.ts

@@ -0,0 +1,135 @@
+import { Address, getBase58Encoder, getBase64Decoder, Signature } from "@solana/kit";
+import { base64BytesToAddress, base64BytesToSignature, base64BytesToString } from "../core/base64-bytes-to";
+
+const base64Decoder = getBase64Decoder();
+const base58Decoder = getBase58Encoder();
+
+describe("base64BytesToAddress", () => {
+  const address = "nick6zJc6HpW3kfBm4xS2dmbuVRyb5F3AnUvj5ymzR5" as Address;
+  const base64BytesForAddress: string = base64Decoder.decode(base58Decoder.encode(address));
+
+  it("should convert valid base64 encoded address bytes to Address", () => {
+    const result = base64BytesToAddress(base64BytesForAddress);
+    expect(result).toBeDefined();
+    expect(typeof result).toBe("string");
+  });
+
+  it("should throw error for invalid base64 string", () => {
+    expect(() => base64BytesToAddress("invalid-base64!!!")).toThrow();
+  });
+
+  it("should throw error for base64 string with incorrect length", () => {
+    // Base64 of only 16 bytes instead of required 32
+    const shortBase64 = "AAAAAAAAAAAAAAAAAAAAAA==";
+    expect(() => base64BytesToAddress(shortBase64)).toThrow();
+  });
+
+  it("should throw error for empty string", () => {
+    expect(() => base64BytesToAddress("")).toThrow();
+  });
+
+  it("should throw error for base64 of invalid address format", () => {
+    // Base64 of 32 bytes but not a valid address format
+    const invalidAddressBase64 = "/////////////////////////////////////w==";
+    expect(() => base64BytesToAddress(invalidAddressBase64)).toThrow();
+  });
+});
+
+describe("base64BytesToSignature", () => {
+  const singature =
+    "4SJT9r8g3ea98CsdagyDSf2pMYjUQrxd9y1DeG5fNFqaQ3gH9N7bhqYUKsn4pZCLKgmhtQek5BFGSnfs2ieS9TMp" as Signature;
+  const base64BytesForSignature: string = base64Decoder.decode(base58Decoder.encode(singature));
+
+  it("should convert valid base64 encoded signature bytes to Signature", () => {
+    const result = base64BytesToSignature(base64BytesForSignature);
+    expect(result).toBeDefined();
+    expect(typeof result).toBe("string");
+  });
+
+  it("should throw error on invalid base64 encoded signature bytes to Signature", () => {
+    // Base64 of 64 bytes with some data, but not a valid Signature
+    const signatureBase64 =
+      "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAgID";
+    expect(() => base64BytesToSignature(signatureBase64)).toThrow();
+  });
+
+  it("should throw error for invalid base64 string", () => {
+    expect(() => base64BytesToSignature("invalid-base64!!!")).toThrow();
+  });
+
+  it("should throw error for base64 string with incorrect length", () => {
+    // Base64 of only 32 bytes instead of required 64
+    const shortBase64 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+    expect(() => base64BytesToSignature(shortBase64)).toThrow();
+  });
+
+  it("should throw error for empty string", () => {
+    expect(() => base64BytesToSignature("")).toThrow();
+  });
+
+  it("should throw error for base64 of invalid signature format", () => {
+    // Base64 of 64 bytes but potentially invalid signature format
+    const invalidSignatureBase64 =
+      "//////////////////////////////////////////////////////////////////////////////////////////8=";
+    expect(() => base64BytesToSignature(invalidSignatureBase64)).toThrow();
+  });
+});
+
+describe("base64BytesToString", () => {
+  it("should convert base64 encoded string bytes to UTF-8 string", () => {
+    const testString = "Hello, World!" as string;
+    const base64BytesForTestString: string = getBase64Decoder().decode(new TextEncoder().encode(testString));
+
+    const result = base64BytesToString(base64BytesForTestString);
+    expect(result).toBe(testString);
+  });
+
+  it("should convert base64 encoded empty string", () => {
+    // Base64 of empty string
+    const emptyBase64 = "";
+    const result = base64BytesToString(emptyBase64);
+    expect(result).toBe("");
+  });
+
+  it("should convert base64 encoded single character", () => {
+    // Base64 of "A"
+    const singleCharBase64 = "QQ==";
+    const result = base64BytesToString(singleCharBase64);
+    expect(result).toBe("A");
+  });
+
+  it("should convert base64 encoded Unicode characters", () => {
+    // Base64 of "🚀" (rocket emoji)
+    const unicodeBase64 = "8J+agA==";
+    const result = base64BytesToString(unicodeBase64);
+    expect(result).toBe("🚀");
+  });
+
+  it("should convert base64 encoded multi-byte UTF-8 characters", () => {
+    // Base64 of "Café"
+    const cafeBase64 = "Q2Fmw6k=";
+    const result = base64BytesToString(cafeBase64);
+    expect(result).toBe("Café");
+  });
+
+  it("should convert base64 encoded Japanese characters", () => {
+    // Base64 of "こんにちは" (hello in Japanese)
+    const japaneseBase64 = "44GT44KT44Gr44Gh44Gv";
+    const result = base64BytesToString(japaneseBase64);
+    expect(result).toBe("こんにちは");
+  });
+
+  it("should convert base64 encoded numbers and special characters", () => {
+    // Base64 of "123!@#$%^&*()"
+    const specialCharsBase64 = "MTIzIUAjJCVeJiooKQ==";
+    const result = base64BytesToString(specialCharsBase64);
+    expect(result).toBe("123!@#$%^&*()");
+  });
+
+  it("should handle base64 encoded newlines and whitespace", () => {
+    // Base64 of "Line 1\nLine 2\t\r"
+    const whitespaceBase64 = "TGluZSAxCkxpbmUgMgkN";
+    const result = base64BytesToString(whitespaceBase64);
+    expect(result).toBe("Line 1\nLine 2\t\r");
+  });
+});

+ 27 - 0
packages/gill/src/core/base64-bytes-to.ts

@@ -0,0 +1,27 @@
+import type { Address, Signature } from "@solana/kit";
+import { assertIsAddress, assertIsSignature, getBase58Decoder, getBase64Encoder } from "@solana/kit";
+
+/**
+ * Takes a base64 encoded string of a byte array, parses, then asserts it as an {@link Address}
+ */
+export function base64BytesToAddress(base64Bytes: string): Address {
+  const maybeAddress = getBase58Decoder().decode(getBase64Encoder().encode(base64Bytes));
+  assertIsAddress(maybeAddress);
+  return maybeAddress;
+}
+
+/**
+ * Takes a base64 encoded string of a byte array, parses, then asserts it as an {@link Signature}
+ */
+export function base64BytesToSignature(base64Bytes: string): Signature {
+  const maybeSignature = getBase58Decoder().decode(getBase64Encoder().encode(base64Bytes));
+  assertIsSignature(maybeSignature);
+  return maybeSignature;
+}
+
+/**
+ * Takes a base64 encoded string of a byte array, parses, then returns as a utf8 string
+ */
+export function base64BytesToString(base64Bytes: string): string {
+  return new TextDecoder().decode(getBase64Encoder().encode(base64Bytes));
+}

+ 1 - 0
packages/gill/src/core/index.ts

@@ -1,5 +1,6 @@
 export { debug, isDebugEnabled } from "./debug";
 
+export * from "./base64-bytes-to";
 export * from "./base64-from-transaction";
 export * from "./base64-to-transaction";
 export * from "./const";