-
Notifications
You must be signed in to change notification settings - Fork 35
feat(spore): dob-render-sdk migration #320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
def2273
600a36e
a077bdd
694ae83
e2fba3d
2204c4f
4bb1040
19962f3
4d0934d
04a9592
cc3f59a
7c4e4f0
5260933
f3a303b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
| }, | ||
|
Comment on lines
65
to
67
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| "packageManager": "pnpm@10.8.1" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { describe, it } from "vitest"; | ||
| import { renderByTokenKey, svgToBase64 } 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); | ||
|
||
| }); | ||
| 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"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { config } from '../config' | ||
| import type { DobDecodeResponse } from '../types' | ||
|
|
||
| export async function dobDecode(tokenKey: string): Promise<DobDecodeResponse> { | ||
| const response = await fetch(config.dobDecodeServerURL, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: JSON.stringify({ | ||
| id: 2, | ||
|
||
| jsonrpc: '2.0', | ||
| method: 'dob_decode', | ||
| params: [tokenKey], | ||
| }), | ||
| }) | ||
| return response.json() | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import type { ParsedTrait } from './traits-parser' | ||
| import { Key } from './constants/key' | ||
|
|
||
| export function getBackgroundColorByTraits( | ||
| traits: ParsedTrait[], | ||
| ): ParsedTrait | undefined { | ||
| return traits.find((trait) => trait.name === 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' | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| 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.rgbpp.io' | ||
| private _queryBtcFsFn: QueryBtcFsFn = async (uri) => { | ||
| return fetch(`https://api.omiga.io/api/v1/nfts/dob_imgs?uri=${uri}`).then( | ||
| (res) => res.json(), | ||
| ) | ||
| } | ||
|
|
||
| private _queryUrlFn = async (url: string) => { | ||
| try { | ||
| const response = await fetch(url) | ||
| const blob = await response.blob() | ||
| return new Promise<IpfsResult>((resolve, reject) => { | ||
| const reader = new FileReader() | ||
| // eslint-disable-next-line func-names | ||
| reader.onload = function () { | ||
| const base64 = this.result as string | ||
| resolve(base64) | ||
| } | ||
| reader.onerror = (error) => { | ||
| reject(error) | ||
| } | ||
| reader.readAsDataURL(blob) | ||
| }) | ||
| } catch (error) { | ||
| throw error | ||
| } | ||
| } | ||
|
|
||
| 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() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export enum Key { | ||
| BgColor = 'prev.bgcolor', | ||
| Prev = 'prev', | ||
| Image = 'IMAGE', | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export const ARRAY_REG = /\(%(.*?)\):(\[.*?\])/ | ||
| export const ARRAY_INDEX_REG = /(\d+)<_>$/ | ||
| export const GLOBAL_TEMPLATE_REG = /^prev<(.*?)>/ | ||
| export const TEMPLATE_REG = /^(.*?)<(.*?)>/ |
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| export * from './render-by-dob-decode-response' | ||
| export * from './traits-parser' | ||
| export * from './svg-to-base64' | ||
| export * from './render-text-svg' | ||
| export * from './render-text-params-parser' | ||
| export * from './render-image-svg' | ||
| export * from './types' | ||
| export * from './render-by-token-key' | ||
| export * from './render-dob-bit' | ||
| export { config } from './config' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import type { DobDecodeResult, RenderPartialOutput } from './types' | ||
| import { traitsParser } from './traits-parser' | ||
| import { renderTextParamsParser } from './render-text-params-parser' | ||
| import type { RenderProps } from './render-text-svg' | ||
| import { renderTextSvg } from './render-text-svg' | ||
| import { renderImageSvg } from './render-image-svg' | ||
| import { renderDob1Svg } from './render-dob1-svg' | ||
| import { Key } from './constants/key' | ||
|
|
||
| export function renderByDobDecodeResponse( | ||
| dob0Data: DobDecodeResult | string, | ||
| props?: Pick<RenderProps, 'font'> & { | ||
| outputType?: 'svg' | ||
| }, | ||
| ) { | ||
| if (typeof dob0Data === 'string') { | ||
| dob0Data = JSON.parse(dob0Data) as DobDecodeResult | ||
| } | ||
| if (typeof dob0Data.render_output === 'string') { | ||
| dob0Data.render_output = JSON.parse( | ||
| dob0Data.render_output, | ||
| ) as RenderPartialOutput[] | ||
| } | ||
| const { traits, indexVarRegister } = traitsParser(dob0Data.render_output) | ||
| for (const trait of traits) { | ||
| if (trait.name === 'prev.type' && trait.value === 'image') { | ||
| return renderImageSvg(traits) | ||
| } | ||
| // TODO: multiple images | ||
| if (trait.name === Key.Image && trait.value instanceof Promise) { | ||
| return renderDob1Svg(trait.value) | ||
| } | ||
| } | ||
| const renderOptions = renderTextParamsParser(traits, indexVarRegister) | ||
| return renderTextSvg({ ...renderOptions, font: props?.font }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { dobDecode } from './api/dobDecode' | ||
| import type { RenderProps } from './render-text-svg' | ||
| import { renderByDobDecodeResponse } from './render-by-dob-decode-response' | ||
|
|
||
| export async function renderByTokenKey( | ||
| tokenKey: string, | ||
| options?: Pick<RenderProps, 'font'> & { | ||
| outputType?: 'svg' | ||
| }, | ||
| ) { | ||
| const dobDecodeResponse = await dobDecode(tokenKey) | ||
| return renderByDobDecodeResponse(dobDecodeResponse.result, options) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| import satori from 'satori' | ||
| import type { DobDecodeResult, RenderPartialOutput } from './types' | ||
| import { traitsParser } from './traits-parser' | ||
| import { base64ToArrayBuffer } from './utils/string' | ||
| import SpaceGroteskBoldBase64 from './fonts/SpaceGrotesk-Bold.base64' | ||
|
|
||
| const iconBase64 = | ||
| 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwMCIgaGVpZ2h0PSIxMDAwIiB2aWV3Qm94PSIwIDAgMTAwMCAxMDAwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDBfNTA0XzI4OCkiPgo8cmVjdCB3aWR0aD0iMTAwMCIgaGVpZ2h0PSIxMDAwIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTAwMCAwSDBWMTAwMEgxMDAwVjBaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNTAwIDY0NS42NjlDNjE1LjE1NyA2NDUuNjY5IDcwOC42NjEgNTUyLjE2NSA3MDguNjYxIDQzNy4wMDhDNzA4LjY2MSAzOTAuMDkyIDY5My4yNDIgMzQ2Ljc4NSA2NjYuOTk1IDMxMi4wMDhDNjkyLjI1NyAyOTIuNjUxIDcwOC42NjEgMjYyLjQ2NyA3MDguNjYxIDIyOC4zNDZINTAwQzM4NC44NDMgMjI4LjM0NiAyOTEuMzM5IDMyMS44NSAyOTEuMzM5IDQzNy4wMDhDMjkxLjMzOSA1NTEuODM3IDM4NS4xNzEgNjQ1LjY2OSA1MDAgNjQ1LjY2OVpNNTAwIDMyMy44MTlDNTYyLjMzNiAzMjMuODE5IDYxMy4xODkgMzc0LjY3MiA2MTMuMTg5IDQzNy4wMDhDNjEzLjE4OSA0OTkuMzQ0IDU2Mi4zMzYgNTUwLjE5NyA1MDAgNTUwLjE5N0M0MzcuNjY0IDU1MC4xOTcgMzg2LjgxMSA0OTkuMzQ0IDM4Ni44MTEgNDM3LjAwOEMzODYuODExIDM3NC42NzIgNDM3LjY2NCAzMjMuODE5IDUwMCAzMjMuODE5WiIgZmlsbD0iIzAwREY5QiIvPgo8cGF0aCBkPSJNNTAwIDgxMS4zNTJDNDA0LjE5OSA4MTEuMzUyIDMwOC4zOTkgNzc0LjkzNCAyMzUuMjM2IDcwMS43NzJDMjQ2LjcxOSA2NzEuNTg4IDI3MS45ODIgNjQ2LjY1NCAzMDUuNzc0IDYzNy4xMzlDMzU5LjkwOCA2ODkuNjMzIDQyOS43OSA3MTUuODc5IDUwMCA3MTUuODc5QzU3MC4yMSA3MTUuODc5IDY0MC4wOTIgNjg5LjYzMyA2OTQuMjI2IDYzNy4xMzlDNzI4LjAxOCA2NDYuNjU0IDc1My4yODEgNjcxLjI2IDc2NC43NjQgNzAxLjc3MkM2OTEuOTI5IDc3NC42MDYgNTk1LjgwMSA4MTEuMzUyIDUwMCA4MTEuMzUyWiIgZmlsbD0iIzI0NzFGRSIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzUwNF8yODgiPgo8cmVjdCB3aWR0aD0iMTAwMCIgaGVpZ2h0PSIxMDAwIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=' | ||
|
|
||
| export function renderDobBit( | ||
| dob0Data: DobDecodeResult | string, | ||
| props?: { | ||
| outputType?: 'svg' | ||
| }, | ||
| ) { | ||
| if (typeof dob0Data === 'string') { | ||
| dob0Data = JSON.parse(dob0Data) as DobDecodeResult | ||
| } | ||
| if (typeof dob0Data.render_output === 'string') { | ||
| dob0Data.render_output = JSON.parse( | ||
| dob0Data.render_output, | ||
| ) as RenderPartialOutput[] | ||
| } | ||
| const { traits } = traitsParser(dob0Data.render_output) | ||
| const account = traits.find((trait) => trait.name === 'Account')?.value ?? '-' | ||
| let fontSize = 76 | ||
| if (typeof account === 'string') { | ||
| if (account.length > 10) { | ||
| fontSize = fontSize / 2 | ||
| } | ||
| if (account.length > 20) { | ||
| fontSize = fontSize / 2 | ||
| } | ||
| if (account.length > 30) { | ||
| fontSize = fontSize * 0.75 | ||
| } | ||
| } | ||
| const spaceGroteskBoldFont = base64ToArrayBuffer(SpaceGroteskBoldBase64) | ||
| return satori( | ||
| { | ||
| key: 'container', | ||
| type: 'div', | ||
| props: { | ||
| style: { | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| width: '500px', | ||
| background: '#3A3A43', | ||
| color: '#fff', | ||
| height: '500px', | ||
| textAlign: 'center', | ||
| }, | ||
| children: [ | ||
| { | ||
| type: 'img', | ||
| props: { | ||
| src: iconBase64, | ||
| width: 100, | ||
| height: 100, | ||
| style: { | ||
| width: '100px', | ||
| height: '100px', | ||
| borderRadius: '100%', | ||
| marginBottom: '40px', | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| type: 'div', | ||
| props: { | ||
| children: account, | ||
| style: { | ||
| marginBottom: '20px', | ||
| fontSize: `${fontSize}px`, | ||
| textAlign: 'center', | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| type: 'div', | ||
| props: { | ||
| children: '.bit', | ||
| style: { | ||
| fontSize: '44px', | ||
| padding: '4px 40px', | ||
| borderRadius: '200px', | ||
| background: 'rgba(255, 255, 255, 0.10)', | ||
| }, | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| }, | ||
| { | ||
| width: 500, | ||
| height: 500, | ||
| fonts: [ | ||
| { | ||
| name: 'SpaceGrotesk', | ||
| data: spaceGroteskBoldFont, | ||
| weight: 700, | ||
| }, | ||
| ], | ||
| }, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
satoriis listed in bothdependenciesandpeerDependencies. 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 providesatori, it should only be inpeerDependencies. If you intend to bundle it, it should only be independencies. Please clarify the intention and adjust the dependencies accordingly.