diff --git a/.changeset/fast-trees-wash.md b/.changeset/fast-trees-wash.md new file mode 100644 index 00000000..9a40fa4b --- /dev/null +++ b/.changeset/fast-trees-wash.md @@ -0,0 +1,5 @@ +--- +'@sei-js/actions': patch +--- + +Added chainType to individual action config interface, added EVm types to POST request response type diff --git a/.changeset/giant-badgers-judge.md b/.changeset/giant-badgers-judge.md new file mode 100644 index 00000000..d8d91518 --- /dev/null +++ b/.changeset/giant-badgers-judge.md @@ -0,0 +1,5 @@ +--- +'@sei-js/actions': patch +--- + +Added newest types diff --git a/.changeset/healthy-icons-divide.md b/.changeset/healthy-icons-divide.md new file mode 100644 index 00000000..47169479 --- /dev/null +++ b/.changeset/healthy-icons-divide.md @@ -0,0 +1,6 @@ +--- +'@slinks/blink-ui': patch +'@sei-js/actions': patch +--- + +Release updated versions diff --git a/.changeset/rich-islands-arrive.md b/.changeset/rich-islands-arrive.md new file mode 100644 index 00000000..c9e9a40a --- /dev/null +++ b/.changeset/rich-islands-arrive.md @@ -0,0 +1,5 @@ +--- +'@sei-js/create-sei': minor +--- + +Adds a new command to create an actions API express application diff --git a/codecov.yml b/codecov.yml index 691f8e7d..2500381d 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,7 +11,12 @@ flags: registry: paths: - packages/registry/** - + create-sei: + paths: + - packages/create-sei/** + actions: + paths: + - packages/actions/** coverage: status: project: @@ -31,3 +36,11 @@ coverage: target: 80% flags: - registry + create-sei: + target: 80% + flags: + - create-sei + actions: + target: 80% + flags: + - actions diff --git a/package.json b/package.json index dc9d1fee..ec721d4d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "name": "sei-js", "version": "2.0.0", - "private": true, "license": "MIT", "workspaces": [ "packages/*" diff --git a/packages/actions/jest.config.ts b/packages/actions/jest.config.ts new file mode 100644 index 00000000..790adc08 --- /dev/null +++ b/packages/actions/jest.config.ts @@ -0,0 +1,14 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +import { getJestProjects } from '@nx/jest'; + +export default { + projects: getJestProjects(), + preset: 'ts-jest', + testMatch: ['**/*.spec.ts', '**/*.spec.tsx'], + globals: { + 'ts-jest': { + tsconfig: './tsconfig.json' + } + }, + modulePathIgnorePatterns: ['/packages/*/dist/'] +}; diff --git a/packages/actions/package.json b/packages/actions/package.json new file mode 100644 index 00000000..5e1b24e7 --- /dev/null +++ b/packages/actions/package.json @@ -0,0 +1,43 @@ +{ + "name": "@sei-js/actions", + "version": "1.0.0", + "description": "TypeScript library for the actions standard on Sei", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "sideEffects": false, + "files": [ + "dist" + ], + "scripts": { + "prebuild": "rimraf dist", + "build": "tsc --project tsconfig.json && yarn build:prettier", + "build:prettier": "prettier --write 'dist/**/*.js'", + "test": "jest", + "lint": "eslint --ext .ts" + }, + "homepage": "https://github.com/sei-protocol/sei-js#readme", + "keywords": [ + "sei", + "javascript", + "typescript", + "registry" + ], + "repository": "git@github.com:sei-protocol/sei-js.git", + "license": "MIT", + "publishConfig": { + "access": "restricted" + }, + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0" + }, + "peerDependencies": {}, + "devDependencies": {}, + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + } + } +} diff --git a/packages/actions/src/__tests__/types.spec.ts b/packages/actions/src/__tests__/types.spec.ts new file mode 100644 index 00000000..039c04f8 --- /dev/null +++ b/packages/actions/src/__tests__/types.spec.ts @@ -0,0 +1,32 @@ +import { GetSeiActionResponse } from '../types'; + +describe('GetSeiActionResponse', () => { + it('should create a valid GetSeiActionResponse object', () => { + const response: GetSeiActionResponse = { + icon: 'https://example.com/icon.png', + label: 'Test Label', + title: 'Test Title', + description: 'Test Description', + transactionType: 'EVM', + links: { + actions: [ + { + label: 'Test Action', + href: '/api/test-action', + parameters: [ + { + type: 'text', + name: 'param1', + label: 'Param 1', + required: true + } + ] + } + ] + } + }; + + expect(response).toHaveProperty('icon', 'https://example.com/icon.png'); + expect(response.links.transactionType).toBe('EVM'); + }); +}); diff --git a/packages/actions/src/index.ts b/packages/actions/src/index.ts new file mode 100644 index 00000000..fcb073fe --- /dev/null +++ b/packages/actions/src/index.ts @@ -0,0 +1 @@ +export * from './types'; diff --git a/packages/actions/src/types.ts b/packages/actions/src/types.ts new file mode 100644 index 00000000..e96ab4a0 --- /dev/null +++ b/packages/actions/src/types.ts @@ -0,0 +1,77 @@ +import type { TransactionRequest } from '@ethersproject/abstract-provider'; + +// Define the type for the actions.json file at the root of a domain +export interface SeiActionsJSON { + rules: Array<{ + pathPattern: string; + apiPath: string; + }>; +} + +// Type for the GET response for a given action +export interface GetSeiActionResponse { + icon: string; // URL pointing to an image describing the action. + label: string; // Text to be displayed on the button used to execute the action. + title: string; // The title of the action. + description: string; // A brief description of the action. + disabled?: boolean; // Optional flag to disable all buttons associated with the action. + transactionType: 'EVM' | 'COSMOS'; // The type of blockchain transaction to be executed. + links: { + actions: SeiActionConfig[]; // An array of SeiActionConfig objects defining the actions available. + }; + error?: SeiActionError; // Error message intended to be displayed to the user. +} + +export interface SeiActionConfig { + label: string; + href: string; + parameters?: (SeiActionParameter | SeiActionParameterSelectable)[]; +} + +export type SeiActionParameterType = 'text' | 'address' | 'email' | 'url' | 'number' | 'date' | 'datetime-local' | 'checkbox' | 'radio' | 'textarea' | 'select'; + +export type SeiActionParameter = { + name: string; + type: SeiActionParameterType; + label: string; + required?: boolean; + /** regular expression pattern to validate user input client side */ + pattern?: string; + /** human-readable description of the `type` and/or `pattern`, represents a caption and error, if value doesn't match */ + patternDescription?: string; + /** the minimum value allowed based on the `type` */ + min?: string | number; + /** the maximum value allowed based on the `type` */ + max?: string | number; +}; + +// Used if parameter is type 'select', 'radio', or 'checkbox' +export interface SeiActionParameterSelectable extends SeiActionParameter { + options: Array<{ + /** displayed UI label of this selectable option */ + label: string; + /** value of this selectable option */ + value: string; + /** whether or not this option should be selected by default */ + selected?: boolean; + }>; +} + +// Define the type for the POST requests to a given action +export interface PostSeiActionRequest { + sender: string; + [key: string]: unknown; +} + +export interface SeiActionError { + /** non-fatal error message to be displayed to the user */ + message: string; +} + +export type SeiActionSuccessResponse = { + transaction: TransactionRequest | any; + message?: string; +}; + +// Define the type for the POST response for a given action +export type PostSeiActionResponse = SeiActionSuccessResponse | SeiActionError; diff --git a/packages/actions/tsconfig.json b/packages/actions/tsconfig.json new file mode 100644 index 00000000..7d9f46ab --- /dev/null +++ b/packages/actions/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["./src/**/*"], + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + } +} diff --git a/packages/blink-ui/package.json b/packages/blink-ui/package.json new file mode 100644 index 00000000..06d57089 --- /dev/null +++ b/packages/blink-ui/package.json @@ -0,0 +1,36 @@ +{ + "name": "@slinks/blink-ui", + "version": "1.0.0", + "description": "A javascript library for rendering Blink UI components for Sei.", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "types": "dist/index.d.ts", + "author": "carson", + "license": "MIT", + "scripts": { + "prebuild": "rimraf dist", + "build": "rollup -c" + }, + "peerDependencies": { + "react": ">=17.0.0 <19.0.0", + "react-dom": ">=17.0.0 <19.0.0" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^14.0.0", + "@rollup/plugin-node-resolve": "^8.0.0", + "@rollup/plugin-typescript": "^11.1.6", + "@types/react": "^18.3.4", + "rollup": "^4.21.1", + "rollup-plugin-dts": "^6.1.1", + "rollup-plugin-peer-deps-external": "^2.2.4", + "rollup-plugin-terser": "^7.0.2", + "tslib": "^2.7.0" + }, + "dependencies": { + "@mantine/core": "^7.12.1", + "@mantine/hooks": "^7.12.1", + "@sei-js/actions": "^1.0.0", + "@tabler/icons-react": "^3.13.0", + "url-pattern": "^1.0.3" + } +} diff --git a/packages/blink-ui/rollup.config.mjs b/packages/blink-ui/rollup.config.mjs new file mode 100644 index 00000000..3780e6d8 --- /dev/null +++ b/packages/blink-ui/rollup.config.mjs @@ -0,0 +1,37 @@ +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import typescript from '@rollup/plugin-typescript'; +import external from 'rollup-plugin-peer-deps-external'; +import dts from 'rollup-plugin-dts'; + +import packageJson from './package.json' assert { type: 'json' }; + +export default [ + { + input: 'src/index.ts', + output: [ + { + file: packageJson.main, + format: 'cjs', + sourcemap: true, + name: packageJson.name + }, + { + file: packageJson.module, + format: 'esm', + sourcemap: true + } + ], + plugins: [ + external(), // To prevent bundling peerDependencies + resolve(), // Resolve third party dependencies in node_modules + commonjs(), // To convert commonjs modules into ES6 + typescript({ tsconfig: './tsconfig.json' }) // To transpile our Typescript code in JS + ], + }, + { + input: 'dist/esm/types/index.d.ts', + output: [{ file: packageJson.types, format: "esm" }], + plugins: [dts()], + } +] diff --git a/packages/blink-ui/src/BlinkCard.tsx b/packages/blink-ui/src/BlinkCard.tsx new file mode 100644 index 00000000..8fb41f84 --- /dev/null +++ b/packages/blink-ui/src/BlinkCard.tsx @@ -0,0 +1,166 @@ +import { Box, Button, Group, Image, Input, Paper, Radio, Stack, Text, Textarea, ThemeIcon, Title, Select, Checkbox } from '@mantine/core'; +import { GetSeiActionResponse, SeiActionConfig, SeiActionParameter, SeiActionParameterSelectable, SeiActionsJSON } from '@sei-js/actions'; +import { IconShieldCheckFilled } from '@tabler/icons-react'; +import React, { useState, useEffect } from 'react'; +import { getMatchingRule, getRootDomain } from './utils'; + +export const BlinkCard = ({ actionUrl, senderAddress }: { actionUrl: string; senderAddress: string }) => { + const [blink, setBlink] = useState(); + + useEffect(() => { + const fetchData = async () => { + try { + const rootDomain = getRootDomain(actionUrl); + const response = await fetch(`${rootDomain}/actions.json`); + const actionsJSON: SeiActionsJSON = await response.json(); + const matchingApiRule = getMatchingRule(actionsJSON, actionUrl); + if (!matchingApiRule) { + return; + } + const apiResponse = await fetch(`${rootDomain}${matchingApiRule.apiPath}`); + const actionResponse: GetSeiActionResponse = await apiResponse.json(); + return actionResponse; + } catch (error) { + console.error('Error fetching action.json:', error); + return; + } + }; + + fetchData().then(setBlink); + }, [actionUrl]); + + const sendAction = () => { + console.log('Sending action', senderAddress); + }; + + const domain = new URL(actionUrl).hostname; + + const renderActionButton = (action: SeiActionConfig) => ( + + ); + + const renderActionInput = (field: SeiActionParameter, index: number) => { + switch (field.type) { + case 'text': + case 'email': + case 'url': + case 'number': + case 'date': + case 'datetime-local': + return ; + case 'textarea': + return