Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/mean-yaks-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ckb-ccc/spore": minor
---

Migrate dob-render-sdk directly into spore module

7 changes: 7 additions & 0 deletions packages/examples/src/renderDob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { spore } from "@ckb-ccc/ccc";

const sporeId =
"dc19e68af1793924845e2a4dbc23598ed919dcfe44d3f9cd90964fe590efb0e4";

const dobRender = await spore.dob.renderByTokenKey(sporeId);
console.log(dobRender);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While this is an example file, using top-level await can limit its reusability in environments that don't support it. It would be more robust to wrap this logic in an async function.

Suggested change
const dobRender = await spore.dob.renderByTokenKey(sporeId);
console.log(dobRender);
async function main() {
const dobRender = await spore.dob.renderByTokenKey(sporeId);
console.log(dobRender);
}
main();

7 changes: 6 additions & 1 deletion packages/spore/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@
},
"dependencies": {
"@ckb-ccc/core": "workspace:*",
"axios": "^1.11.0"
"axios": "^1.11.0",
"satori": "^0.10.13",
"svgson": "^5.3.1"
},
"peerDependencies": {
"satori": "^0.10.13"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

satori is listed in both dependencies and peerDependencies. This is generally an anti-pattern and can lead to version conflicts and unexpected behavior for consumers of this library. If you expect the consumer to provide satori, it should only be in peerDependencies. If you intend to bundle it, it should only be in dependencies. Please clarify the intention and adjust the dependencies accordingly.

},
Comment on lines +65 to 67
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Having satori in both dependencies and peerDependencies is unconventional and can lead to versioning issues. If satori is a core dependency for this package, it should only be listed in dependencies. Please remove it from peerDependencies to avoid potential conflicts for consumers of this library.

"packageManager": "pnpm@10.8.1"
}
14 changes: 14 additions & 0 deletions packages/spore/src/__examples__/renderDob.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { describe, it } from "vitest";
import { renderByTokenKey } from "../dob/index.js";

describe("decodeDob [testnet]", () => {
it("should respose a decoded dob render data from a spore id", async () => {
// The spore id that you want to decode (must be a valid spore dob)
const sporeId =
"dc19e68af1793924845e2a4dbc23598ed919dcfe44d3f9cd90964fe590efb0e4";

// Decode from spore id
const dob = await renderByTokenKey(sporeId);
console.log(dob);
}, 60000);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test case lacks assertions. A test should verify that the code behaves as expected, but this one only logs the result to the console. It will pass as long as no error is thrown. Please add expect assertions to validate the output of renderByTokenKey. Also, console.log should be removed from test files before merging.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test case uses console.log to manually inspect the output but lacks automated assertions to verify the correctness of the renderByTokenKey function's output. Tests should have explicit assertions to be effective for regression testing.

Additionally, the test description "should respose" contains a typo and should be "should respond".

  it("should respond with decoded DOB render data from a spore id", async () => {
    // The spore id that you want to decode (must be a valid spore dob)
    const sporeId =
      "dc19e68af1793924845e2a4dbc23598ed919dcfe44d3f9cd90964fe590efb0e4";

    // Decode from spore id
    const dob = await renderByTokenKey(sporeId);
    expect(dob).toBeDefined();
    // TODO: Add more specific assertions about the 'dob' content to make this a more effective test.
  }, 60000);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This test is missing assertions to verify the output. It currently only checks that renderByTokenKey doesn't throw an error. Tests should include expect statements to validate the function's output against expected results. Also, there's a typo in the description ('respose' should be 'respond').

  it("should respond with a decoded dob render data from a spore id", async () => {
    // The spore id that you want to decode (must be a valid spore dob)
    const sporeId =
      "dc19e68af1793924845e2a4dbc23598ed919dcfe44d3f9cd90964fe590efb0e4";

    // Decode from spore id
    const dob = await renderByTokenKey(sporeId);
    console.log(dob);
    // TODO: Add assertions to validate the `dob` content.
    // For example: expect(dob).toContain('<svg');
  }, 60000);

});
1 change: 1 addition & 0 deletions packages/spore/src/dob/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./api/index.js";
export * from "./config/index.js";
export * from "./helper/index.js";
export * from "./render/index.js";
2 changes: 2 additions & 0 deletions packages/spore/src/dob/render/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./renderDobDecode.js";
export * from "./renderToken.js";
35 changes: 35 additions & 0 deletions packages/spore/src/dob/render/api/renderDobDecode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { RenderOutput } from "../../helper/object.js";
import { Key } from "../config/constants.js";
import { renderTextParamsParser } from "../core/parsers/textParamsParser.js";
import { traitsParser } from "../core/parsers/traitsParser.js";
import { renderDob1Svg } from "../core/renderers/dob1Render.js";
import { renderImageSvg } from "../core/renderers/imageRender.js";
import type { RenderProps } from "../core/renderers/textRender.js";
import { renderTextSvg } from "../core/renderers/textRender.js";

export function renderByDobDecodeResponse(
renderOutput: RenderOutput | string,
props?: Pick<RenderProps, "font"> & {
outputType?: "svg";
},
) {
let renderData: RenderOutput;
if (typeof renderOutput === "string") {
renderData = JSON.parse(renderOutput) as RenderOutput;
} else {
renderData = renderOutput;
}

const { traits, indexVarRegister } = traitsParser(renderData);
for (const trait of traits) {
if (trait.name === String(Key.Type) && trait.value === "image") {
return renderImageSvg(traits);
}
// TODO: multiple images
if (trait.name === String(Key.Image) && trait.value instanceof Promise) {
return renderDob1Svg(trait.value);
}
}
const renderOptions = renderTextParamsParser(traits, indexVarRegister);
return renderTextSvg({ ...renderOptions, font: props?.font });
}
18 changes: 18 additions & 0 deletions packages/spore/src/dob/render/api/renderToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { decodeDobBySporeId } from "../../api/decode.js";
import { config } from "../config.js";
import type { RenderProps } from "../core/renderers/textRender.js";
import { renderByDobDecodeResponse } from "./renderDobDecode.js";

export async function renderByTokenKey(
tokenKey: string,
options?: Pick<RenderProps, "font"> & {
outputType?: "svg";
},
) {
const renderOutput = await decodeDobBySporeId(
tokenKey,
config.dobDecodeServerURL,
);

return renderByDobDecodeResponse(renderOutput, options);
}
85 changes: 85 additions & 0 deletions packages/spore/src/dob/render/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
export type FileServerResult =
| string
| {
content: string;
content_type: string;
};

export type BtcFsResult = FileServerResult;
export type IpfsResult = FileServerResult;

export type BtcFsURI = `btcfs://${string}`;
export type IpfsURI = `ipfs://${string}`;

export type QueryBtcFsFn = (uri: BtcFsURI) => Promise<BtcFsResult>;
export type QueryIpfsFn = (uri: IpfsURI) => Promise<IpfsResult>;
export type QueryUrlFn = (uri: string) => Promise<FileServerResult>;

export class Config {
private _dobDecodeServerURL = "https://dob-decoder.ckbccc.com";
private _queryBtcFsFn: QueryBtcFsFn = async (uri) => {
console.log("dob-render-sdk requiring", uri);
const response = await fetch(
`https://dob-decoder.ckbccc.com/restful/dob_extract_image?uri=${uri}&encode=base64`,
);
const text = await response.text();
return {
content: text,
content_type: "",
};
};

private _queryUrlFn = async (url: string) => {
console.log("dob-render-sdk requiring", url);
const response = await fetch(url);
const blob = await response.blob();
return new Promise<IpfsResult>((resolve, reject) => {
const reader = new FileReader();

reader.onload = function () {
const base64 = this.result as string;
resolve(base64);
};
reader.onerror = (error) => {
reject(new Error(`FileReader error: ${error.type}`));
};
reader.readAsDataURL(blob);
});
};

private _queryIpfsFn = async (uri: IpfsURI) => {
const key = uri.substring("ipfs://".length);
const url = `https://ipfs.io/ipfs/${key}`;
return this._queryUrlFn(url);
};

get dobDecodeServerURL() {
return this._dobDecodeServerURL;
}

setDobDecodeServerURL(dobDecodeServerURL: string): void {
this._dobDecodeServerURL = dobDecodeServerURL;
}

setQueryBtcFsFn(fn: QueryBtcFsFn): void {
this._queryBtcFsFn = fn;
}

setQueryIpfsFn(fn: QueryIpfsFn): void {
this._queryIpfsFn = fn;
}

get queryBtcFsFn(): QueryBtcFsFn {
return this._queryBtcFsFn;
}

get queryIpfsFn(): QueryIpfsFn {
return this._queryIpfsFn;
}

get queryUrlFn(): QueryUrlFn {
return this._queryUrlFn;
}
}

export const config = new Config();
12 changes: 12 additions & 0 deletions packages/spore/src/dob/render/config/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export enum Key {
Bg = "prev.bg",
Type = "prev.type",
BgColor = "prev.bgcolor",
Prev = "prev",
Image = "IMAGE",
}

export const ARRAY_REG = /\(%(.*?)\):(\[.*?\])/;
export const ARRAY_INDEX_REG = /(\d+)<_>$/;
export const GLOBAL_TEMPLATE_REG = /^prev<(.*?)>/;
export const TEMPLATE_REG = /^(.*?)<(.*?)>/;
9 changes: 9 additions & 0 deletions packages/spore/src/dob/render/config/fonts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import SpaceGroteskBoldBase64 from "../fonts/spaceGroteskBold.base64.js";
import TurretRoadBoldBase64 from "../fonts/turretRoadBold.base64.js";
import TurretRoadMediumBase64 from "../fonts/turretRoadMedium.base64.js";

export const FONTS = {
SpaceGroteskBold: SpaceGroteskBoldBase64,
TurretRoadBold: TurretRoadBoldBase64,
TurretRoadMedium: TurretRoadMediumBase64,
} as const;
3 changes: 3 additions & 0 deletions packages/spore/src/dob/render/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { config } from "../config.js";
export * from "./constants.js";
export * from "./fonts.js";
2 changes: 2 additions & 0 deletions packages/spore/src/dob/render/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./parsers/index.js";
export * from "./renderers/index.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Key } from "../../config/constants.js";
import type { ParsedTrait } from "../../types/core.js";

export function getBackgroundColorByTraits(
traits: ParsedTrait[],
): ParsedTrait | undefined {
return traits.find((trait) => trait.name === String(Key.BgColor));
}

export function backgroundColorParser(
traits: ParsedTrait[],
options?: {
defaultColor?: string;
},
): string {
const bgColorTrait = getBackgroundColorByTraits(traits);
if (bgColorTrait) {
if (typeof bgColorTrait.value === "string") {
if (
bgColorTrait.value.startsWith("#(") &&
bgColorTrait.value.endsWith(")")
) {
return bgColorTrait.value.replace("#(", "linear-gradient(");
}
return bgColorTrait.value;
}
}
return options?.defaultColor || "#000";
}
4 changes: 4 additions & 0 deletions packages/spore/src/dob/render/core/parsers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./backgroundColorParser.js";
export * from "./styleParser.js";
export * from "./textParamsParser.js";
export * from "./traitsParser.js";
Loading