├── packages ├── xrpl │ ├── .env.example │ ├── src │ │ ├── index.ts │ │ ├── api │ │ │ ├── index.ts │ │ │ └── XRPLApi.ts │ │ ├── vwbl │ │ │ └── index.ts │ │ ├── types │ │ │ ├── index.ts │ │ │ └── ConstructorPropsType.ts │ │ ├── public │ │ │ └── sdk-flow.png │ │ └── blockchain │ │ │ └── VWBLProtocol.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ └── examples │ │ └── vwbl-xrpl.ts ├── core │ ├── src │ │ ├── vwbl │ │ │ ├── metadata │ │ │ │ ├── index.ts │ │ │ │ └── type.ts │ │ │ ├── types │ │ │ │ ├── EncryptLogic.ts │ │ │ │ ├── UploadContentType.ts │ │ │ │ ├── UploadMetadataType.ts │ │ │ │ ├── ManageKeyType.ts │ │ │ │ ├── index.ts │ │ │ │ ├── ProgressSubscriber.ts │ │ │ │ ├── ConstructorPropsType.ts │ │ │ │ └── File.ts │ │ │ ├── index.ts │ │ │ └── core.ts │ │ ├── storage │ │ │ ├── index.ts │ │ │ ├── aws │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── upload.ts │ │ │ └── ipfs │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── upload.ts │ │ ├── index.ts │ │ └── util │ │ │ ├── index.ts │ │ │ ├── envUtil.ts │ │ │ ├── fileHelper.ts │ │ │ └── cryptoHelper.ts │ ├── .gitignore │ ├── bable.config.js │ ├── tsconfig.json │ ├── .editorconfig │ ├── README.md │ └── package.json └── evm │ ├── src │ ├── vwbl │ │ ├── api │ │ │ ├── index.ts │ │ │ └── VWBLApi.ts │ │ ├── types │ │ │ ├── MetaTxConfigType.ts │ │ │ ├── GasSettings.ts │ │ │ ├── index.ts │ │ │ ├── TxParamType.ts │ │ │ ├── ConstructorPropsType.ts │ │ │ └── VWBLERC721ERC6150Type.ts │ │ ├── blockchain │ │ │ ├── index.ts │ │ │ ├── Sign.ts │ │ │ ├── erc6150 │ │ │ │ ├── VWBLMetaTxProtocol.ts │ │ │ │ ├── VWBLProtocolEthers.ts │ │ │ │ └── VWBLProtocol.ts │ │ │ ├── erc721 │ │ │ │ ├── VWBLProtocolEthers.ts │ │ │ │ └── VWBLProtocol.ts │ │ │ └── erc1155 │ │ │ │ └── VWBLProtocolEthers.ts │ │ ├── index.ts │ │ └── base.ts │ ├── util │ │ ├── index.ts │ │ ├── transactionHelper.ts │ │ └── biconomyHelper.ts │ ├── index.ts │ └── contract │ │ └── Forwarder.json │ ├── .gitignore │ ├── test │ ├── asset │ │ ├── plain.png │ │ └── thumbnail.png │ └── large │ │ └── vwbl │ │ ├── mintOnPolygon.test.ts │ │ ├── mintOnAmoy.test.ts │ │ ├── mintOnSepolia.test.ts │ │ └── mintOnGoerli.test.ts │ ├── .env.template │ ├── tsconfig.json │ ├── babel.config.js │ ├── jest.config.js │ ├── web-test-runner.config.js │ ├── package.json │ └── README.md ├── .eslintignore ├── .gitignore ├── .editorconfig ├── eslint.config.mjs ├── nx.json ├── .github └── workflows │ ├── ci.yml │ ├── check-can-publish.yml │ └── publish.yml ├── package.json ├── .nx └── nxw.js ├── tsconfig.base.json └── LICENSE /packages/xrpl/.env.example: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/xrpl/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./vwbl"; 2 | -------------------------------------------------------------------------------- /packages/xrpl/src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./XRPLApi"; 2 | -------------------------------------------------------------------------------- /packages/xrpl/src/vwbl/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./VWBLXRPL"; 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .eslintrc.* 3 | **/test/**/* 4 | -------------------------------------------------------------------------------- /packages/core/src/vwbl/metadata/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./type"; 2 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./VWBLApi"; 2 | -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | dist 4 | .DS_Store -------------------------------------------------------------------------------- /packages/evm/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | dist 4 | .env 5 | .DS_Store -------------------------------------------------------------------------------- /packages/xrpl/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ConstructorPropsType"; 2 | -------------------------------------------------------------------------------- /packages/core/src/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./aws"; 2 | export * from "./ipfs"; 3 | -------------------------------------------------------------------------------- /packages/xrpl/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | dist 4 | .DS_Store 5 | 6 | *.env 7 | -------------------------------------------------------------------------------- /packages/core/src/storage/aws/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | export * from "./upload"; 3 | -------------------------------------------------------------------------------- /packages/core/src/storage/ipfs/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | export * from "./upload"; 3 | -------------------------------------------------------------------------------- /packages/core/src/vwbl/types/EncryptLogic.ts: -------------------------------------------------------------------------------- 1 | export type EncryptLogic = "binary" | "base64"; 2 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./vwbl"; 2 | export * from "./util"; 3 | export * from "./storage"; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | .nx/installation 4 | .nx/cache 5 | .nx/workspace-data 6 | 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /packages/evm/src/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./biconomyHelper"; 2 | export * from "./transactionHelper"; 3 | -------------------------------------------------------------------------------- /packages/core/src/vwbl/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./metadata"; 2 | export * from "./types"; 3 | export * from "./core"; -------------------------------------------------------------------------------- /packages/evm/test/asset/plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VWBL/VWBL-SDK/HEAD/packages/evm/test/asset/plain.png -------------------------------------------------------------------------------- /packages/evm/test/asset/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VWBL/VWBL-SDK/HEAD/packages/evm/test/asset/thumbnail.png -------------------------------------------------------------------------------- /packages/xrpl/src/public/sdk-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VWBL/VWBL-SDK/HEAD/packages/xrpl/src/public/sdk-flow.png -------------------------------------------------------------------------------- /packages/core/src/storage/ipfs/types.ts: -------------------------------------------------------------------------------- 1 | export type IPFSConfig = { 2 | apiKey: string; 3 | apiSecret?: string; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/core/src/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./cryptoHelper"; 2 | export * from "./fileHelper"; 3 | export * from "./envUtil"; -------------------------------------------------------------------------------- /packages/core/src/vwbl/types/UploadContentType.ts: -------------------------------------------------------------------------------- 1 | export enum UploadContentType { 2 | S3, 3 | IPFS, 4 | CUSTOM, 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/src/vwbl/types/UploadMetadataType.ts: -------------------------------------------------------------------------------- 1 | export enum UploadMetadataType { 2 | S3, 3 | IPFS, 4 | CUSTOM, 5 | } 6 | -------------------------------------------------------------------------------- /packages/evm/.env.template: -------------------------------------------------------------------------------- 1 | PROVIDER_URL= 2 | PRIVATE_KEY= 3 | NFT_CONTRACT_ADDRESS= 4 | VWBL_NETWORK_URL= 5 | PINATA_API_KEY= 6 | PINATA_API_SECRET= -------------------------------------------------------------------------------- /packages/evm/src/vwbl/types/MetaTxConfigType.ts: -------------------------------------------------------------------------------- 1 | export type MetaTxConfig = { 2 | forwarderAddress: string; 3 | metaTxEndpoint: string; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/core/bable.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"], 3 | }; -------------------------------------------------------------------------------- /packages/evm/src/vwbl/types/GasSettings.ts: -------------------------------------------------------------------------------- 1 | export type GasSettings = { 2 | gasPrice?: number; 3 | maxPriorityFeePerGas?: number; 4 | maxFeePerGas?: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/xrpl/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/evm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | }, 6 | "include": ["src"] 7 | } -------------------------------------------------------------------------------- /packages/evm/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', {targets: {node: 'current'}}], 4 | '@babel/preset-typescript', 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /packages/core/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 -------------------------------------------------------------------------------- /packages/core/src/vwbl/types/ManageKeyType.ts: -------------------------------------------------------------------------------- 1 | export enum ManageKeyType { 2 | VWBL_NETWORK_SERVER, 3 | // VWBL_NETWROK_SERVER is only in use now 4 | VWBL_NETWORK_CONSORTIUM, 5 | MY_SERVER, 6 | } 7 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from '@eslint/js'; 4 | import tseslint from 'typescript-eslint'; 5 | 6 | export default tseslint.config( 7 | eslint.configs.recommended, 8 | ...tseslint.configs.recommended, 9 | ); -------------------------------------------------------------------------------- /packages/evm/src/vwbl/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./MetaTxConfigType"; 2 | export * from "./ConstructorPropsType"; 3 | export * from "./GasSettings"; 4 | export * from "./TxParamType"; 5 | export * from "./VWBLERC721ERC6150Type"; 6 | -------------------------------------------------------------------------------- /packages/evm/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | "^.+\\.ts?$": "ts-jest" 4 | }, 5 | testEnvironment: "node", 6 | testMatch: ['**/unit/**/*.test.ts'], 7 | transformIgnorePatterns: ['/node_modules'] 8 | }; 9 | -------------------------------------------------------------------------------- /packages/evm/web-test-runner.config.js: -------------------------------------------------------------------------------- 1 | const { esbuildPlugin } = require("@web/dev-server-esbuild"); 2 | 3 | module.exports = { 4 | files: "./test/web/**/*.test.ts", 5 | plugins: [esbuildPlugin({ ts: true })], 6 | nodeResolve: true, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/core/src/storage/aws/types.ts: -------------------------------------------------------------------------------- 1 | export type AWSConfig = { 2 | region: string; 3 | idPoolId?: string; 4 | profile?: string; 5 | bucketName: { 6 | content?: string; 7 | metadata?: string; 8 | }; 9 | cloudFrontUrl: string; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/core/src/util/envUtil.ts: -------------------------------------------------------------------------------- 1 | export function isRunningOnNode(): boolean { 2 | return typeof process !== "undefined" && process.versions != null && process.versions.node != null; 3 | } 4 | 5 | export function isRunningOnBrowser(): boolean { 6 | return typeof window !== "undefined"; 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/src/vwbl/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ManageKeyType"; 2 | export * from "./UploadContentType"; 3 | export * from "./UploadMetadataType"; 4 | export * from "./File"; 5 | export * from "./EncryptLogic"; 6 | export * from "./ProgressSubscriber"; 7 | export * from "./ConstructorPropsType"; -------------------------------------------------------------------------------- /packages/evm/src/util/transactionHelper.ts: -------------------------------------------------------------------------------- 1 | export const getFeeSettingsBasedOnEnvironment = ( 2 | maxPriorityFeePerGas: number | undefined, 3 | maxFeePerGas: number | undefined 4 | ) => { 5 | const isRunningOnNode = typeof window === "undefined"; 6 | return isRunningOnNode ? { maxPriorityFeePerGas, maxFeePerGas } : { maxPriorityFeePerGas: null, maxFeePerGas: null }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/core/src/vwbl/types/ProgressSubscriber.ts: -------------------------------------------------------------------------------- 1 | export enum StepStatus { 2 | MINT_TOKEN = "MINT_TOKEN", 3 | CREATE_KEY = "CREATE_KEY", 4 | ENCRYPT_DATA = "ENCRYPT_DATA", 5 | UPLOAD_CONTENT = "UPLOAD_CONTENT", 6 | UPLOAD_METADATA = "UPLOAD_METADATA", 7 | SET_KEY = "SET_KEY", 8 | } 9 | 10 | export type ProgressSubscriber = { 11 | kickStep: (status: StepStatus) => void; 12 | kickProgress?: () => void; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/xrpl/src/types/ConstructorPropsType.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IPFSConfig, 3 | AWSConfig, 4 | UploadContentType, 5 | UploadMetadataType, 6 | } from "vwbl-core"; 7 | 8 | export type XrplConstructorProps = { 9 | xrplChainId: number; 10 | vwblNetworkUrl: string; 11 | uploadContentType?: UploadContentType; 12 | uploadMetadataType?: UploadMetadataType; 13 | awsConfig?: AWSConfig; 14 | ipfsConfig?: IPFSConfig; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/blockchain/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Sign"; 2 | export * from "./erc721/VWBLProtocol"; 3 | export * from "./erc721/VWBLMetaTxProtocol"; 4 | export * from "./erc721/VWBLProtocolEthers"; 5 | export * from "./erc1155/VWBLProtocol"; 6 | export * from "./erc1155/VWBLProtocolEthers"; 7 | export * from "./erc6150/VWBLProtocol"; 8 | export * from "./erc6150/VWBLProtocolEthers"; 9 | export * from "./erc6150/VWBLMetaTxProtocol"; 10 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./erc721/VWBL"; 2 | export * from "./erc721/VWBLMetaTx"; 3 | export * from "./erc721/VWBLEthers"; 4 | export * from "./erc1155/VWBL"; 5 | export * from "./erc1155/VWBLEthers"; 6 | export * from "./erc6150/VWBL"; 7 | export * from "./erc6150/VWBLMetaTx"; 8 | export * from "./types"; 9 | export * from "./blockchain"; 10 | export * from "./api"; 11 | export * from "./base"; 12 | export * from "./viewer"; 13 | -------------------------------------------------------------------------------- /packages/core/src/vwbl/types/ConstructorPropsType.ts: -------------------------------------------------------------------------------- 1 | import { IPFSConfig } from "../../storage/ipfs/types"; 2 | import { AWSConfig } from "../../storage/aws/types"; 3 | import { UploadContentType } from "./UploadContentType"; 4 | import { UploadMetadataType } from "./UploadMetadataType"; 5 | 6 | export type CoreConstructorProps = { 7 | uploadContentType: UploadContentType; 8 | uploadMetadataType: UploadMetadataType; 9 | awsConfig?: AWSConfig; 10 | ipfsConfig?: IPFSConfig; 11 | }; -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "installation": { 3 | "version": "19.3.0" 4 | }, 5 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 6 | "targetDefaults": { 7 | "build": { 8 | "dependsOn": [ 9 | "^build" 10 | ], 11 | "outputs": [ 12 | "{projectRoot}/dist" 13 | ], 14 | "cache": true 15 | }, 16 | "typecheck": { 17 | "cache": true 18 | }, 19 | "lint": { 20 | "cache": true 21 | } 22 | }, 23 | "release": { 24 | "projectsRelationship": "independent" 25 | }, 26 | "defaultBase": "master" 27 | } -------------------------------------------------------------------------------- /packages/evm/src/vwbl/types/TxParamType.ts: -------------------------------------------------------------------------------- 1 | import { GasSettings } from "./GasSettings"; 2 | 3 | export type MintTxParam = { 4 | decryptUrl: string; 5 | feeNumerator: number; 6 | documentId: string; 7 | gasSettings?: GasSettings; 8 | // param of ERC6150 9 | parentId?: number; 10 | }; 11 | 12 | export type MintForIPFSTxParam = MintTxParam & { 13 | metadataUrl: string; 14 | }; 15 | 16 | export type GrantViewPermissionTxParam = { 17 | tokenId: number; 18 | grantee: string; 19 | gasSettings?: GasSettings; 20 | // param of ERC6150 21 | toDir?: boolean; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # VWBL core tools for sub-package 2 | 3 | [![npm version](https://badge.fury.io/js/vwbl-core.svg)](https://badge.fury.io/js/vwbl-core) [![npm download](https://img.shields.io/npm/dt/vwbl-core.svg)](https://img.shields.io/npm/dt/vwbl-core.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | `vwbl-core` package contains core functions for vwbl sdk packages. 6 | 7 | ### install 8 | 9 | ##### Using NPM 10 | `npm install vwbl-core` 11 | 12 | ##### Using Yarn 13 | `yarn add vwbl-core` 14 | 15 | ### Build 16 | 17 | `npm run build:core` 18 | -------------------------------------------------------------------------------- /packages/evm/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./vwbl"; 2 | export * from "./util"; 3 | 4 | export { 5 | AWSConfig, 6 | IPFSConfig, 7 | PlainMetadata, 8 | Metadata, 9 | ExtractMetadata, 10 | ExtendedMetadata, 11 | EncryptLogic, 12 | UploadEncryptedFile, 13 | UploadThumbnail, 14 | UploadMetadata, 15 | UploadEncryptedFileToIPFS, 16 | UploadThumbnailToIPFS, 17 | UploadMetadataToIPFS, 18 | FileOrPath, 19 | Base64DataUrl, 20 | ManageKeyType, 21 | StepStatus, 22 | ProgressSubscriber, 23 | UploadContentType, 24 | UploadMetadataType 25 | } from "vwbl-core"; 26 | export * as core from "vwbl-core"; 27 | -------------------------------------------------------------------------------- /packages/xrpl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vwbl-sdk-xrpl", 3 | "description": "VWBL SDK for XRPL", 4 | "version": "0.1.1", 5 | "main": "dist/packages/xrpl/src/index.js", 6 | "types": "dist/packages/xrpl/src/index.d.ts", 7 | "engines": { 8 | "node": ">=20.0.0" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "license": "MIT", 14 | "dependencies": { 15 | "axios": "^1.6.8", 16 | "xrpl": "^3.0.0", 17 | "xumm-sdk": "^1.11.1", 18 | "vwbl-core": "0.1.0" 19 | }, 20 | "scripts": { 21 | "build": "tsc", 22 | "format": "eslint --fix --quiet './src/**/*.ts'", 23 | "lint": "eslint './src/**/*.ts'" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/blockchain/Sign.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { Web3 } from "web3"; 3 | 4 | interface IEthersSigner { 5 | signMessage(message: string | ethers.utils.Bytes): Promise; 6 | } 7 | 8 | const isEthersSigner = (signer: IEthersSigner): signer is IEthersSigner => { 9 | return signer.signMessage !== undefined; 10 | }; 11 | 12 | export const signToProtocol = async (signer: Web3 | ethers.Signer, signMessage: string) => { 13 | if (isEthersSigner(signer as IEthersSigner)) { 14 | return await (signer as IEthersSigner).signMessage(signMessage); 15 | } else { 16 | const myAddress = (await (signer as Web3).eth.getAccounts())[0]; 17 | return await (signer as Web3).eth.personal.sign(signMessage, myAddress, ""); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /packages/core/src/vwbl/metadata/type.ts: -------------------------------------------------------------------------------- 1 | // use snake case because OpenSea's metadata standard is snake case. 2 | import * as Stream from "stream"; 3 | 4 | import { EncryptLogic } from "../types"; 5 | 6 | export type PlainMetadata = { 7 | name: string; 8 | description: string; 9 | image: string; 10 | encrypted_data: string[]; 11 | mime_type: string; 12 | encrypt_logic: EncryptLogic; 13 | }; 14 | 15 | export type Metadata = { 16 | id: number; 17 | name: string; 18 | description: string; 19 | image: string; 20 | mimeType: string; 21 | encryptLogic: EncryptLogic; 22 | }; 23 | 24 | export type ExtractMetadata = Metadata & { 25 | fileName: string; 26 | ownDataBase64: string[]; 27 | ownFiles: ArrayBuffer[] | Stream[]; 28 | }; 29 | 30 | export type ExtendedMetadata = Metadata & { 31 | address: string; 32 | }; 33 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Lint & Test 2 | 3 | on: 4 | pull_request: 5 | branches: ["*"] 6 | 7 | jobs: 8 | ci: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [20.x] 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup node 20 | uses: actions/setup-node@v3 21 | with: 22 | cache: "npm" 23 | node-version: ${{ matrix.node-version }} 24 | 25 | - name: Cache node_modules 26 | uses: actions/cache@v3 27 | id: cache_node_modules 28 | with: 29 | # check diff of package.json and package-lock.json 30 | key: ${{ runner.os }}-build-node_modules-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/package.json') }} 31 | path: "**/node_modules" 32 | 33 | - name: Install dependencies 34 | if: steps.cache_node_modules.outputs.cache-hit != 'true' 35 | run: npm install 36 | 37 | - name: Build 38 | run: npm run build:all 39 | 40 | - name: Lint 41 | run: npm run lint:all 42 | 43 | - name: Test 44 | run: npm run test:evm 45 | env: 46 | PROVIDER_URL: https://goerli.infura.io/v3/${{ secrets.INFURA_KEY }} 47 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/api/VWBLApi.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export class VWBLApi { 4 | private instance; 5 | constructor(endpointUrl: string) { 6 | this.instance = axios.create({ baseURL: endpointUrl }); 7 | } 8 | async setKey( 9 | documentId: string, 10 | chainId: number, 11 | key: string, 12 | signature: string, 13 | address?: string, 14 | hasNonce?: boolean, 15 | autoMigration?: boolean 16 | ) { 17 | await this.instance.post("/keys", { 18 | document_id: documentId, 19 | chain_id: chainId, 20 | key, 21 | signature, 22 | address, 23 | has_nonce: hasNonce, 24 | auto_migration: autoMigration, 25 | }); 26 | } 27 | 28 | async getKey(documentId: string, chainId: number, signature: string, address?: string): Promise { 29 | const response = await this.instance.get( 30 | `/keys/${documentId}/${chainId}?signature=${signature}&address=${address}` 31 | ); 32 | return response.data.documentKey.key; 33 | } 34 | 35 | async getSignMessage(contractAddress: string, chainId: number, address?: string): Promise { 36 | const response = await this.instance.get(`/signature/${contractAddress}/${chainId}?address=${address}`); 37 | return response.data.signMessage; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vwbl-core", 3 | "description": "VWBL core tools for sub-package", 4 | "version": "0.1.0", 5 | "main": "dist/packages/core/src/index.js", 6 | "types": "dist/packages/core/src/index.d.ts", 7 | "engines": { 8 | "node": ">=20.0.0" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "license": "MIT", 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/VWBL/VWBL-SDK.git" 20 | }, 21 | "scripts": { 22 | "build": "tsc", 23 | "format": "eslint --fix --quiet './src/**/*.ts*'", 24 | "lint": "eslint './src/**/*.ts*'" 25 | }, 26 | "devDependencies": { 27 | "@types/crypto-js": "^4.0.2", 28 | "@types/mime-types": "^2.1.4", 29 | "@types/uuid": "^8.3.3", 30 | "typescript": "^5.3.3" 31 | }, 32 | "dependencies": { 33 | "@aws-sdk/client-s3": "^3.338.0", 34 | "@aws-sdk/credential-providers": "^3.338.0", 35 | "@aws-sdk/lib-storage": "^3.338.0", 36 | "crypto": "^1.0.1", 37 | "crypto-js": "^4.1.1", 38 | "mime-types": "^2.1.35", 39 | "uuid": "^8.3.2" 40 | }, 41 | "browser": { 42 | "crypto": false, 43 | "filereader": false, 44 | "fs": false, 45 | "os": false, 46 | "path": false 47 | }, 48 | "react-native": { 49 | "fs": false, 50 | "path": false, 51 | "os": false, 52 | "filereader": false, 53 | "crypto": false 54 | } 55 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vwbl-sdk.js", 3 | "description": "VWBL SDK for TypeScript", 4 | "version": "1.0.0", 5 | "repository": "https://github.com/VWBL/VWBL-SDK.git", 6 | "license": "MIT", 7 | "workspaces": { 8 | "packages": [ 9 | "packages/*" 10 | ], 11 | "nohoist": [ 12 | "**" 13 | ] 14 | }, 15 | "scripts": { 16 | "build:all": "nx run-many --target=build --all", 17 | "build:core": "nx build vwbl-core", 18 | "build:evm": "nx build vwbl-sdk", 19 | "build:xrpl": "nx build vwbl-sdk-xrpl", 20 | "lint:all": "npm run lint -ws", 21 | "lint:core": "npm run lint -w vwbl-core", 22 | "lint:evm": "npm run lint -w vwbl-sdk", 23 | "format:all": "npm run format -ws", 24 | "format:core": "npm run format -w vwbl-core", 25 | "format:evm": "npm run format -w vwbl-sdk", 26 | "test:evm": "npm run test -w vwbl-sdk", 27 | "publish:core": "nx release publish --projects vwbl-core", 28 | "publish:evm": "nx release publish --projects vwbl-sdk", 29 | "publish:xrpl": "nx release publish --projects vwbl-sdk-xrpl" 30 | }, 31 | "devDependencies": { 32 | "@eslint/js": "^9.4.0", 33 | "@nx/js": "19.3.0", 34 | "@types/eslint__js": "^8.42.3", 35 | "eslint": "^8.57.0", 36 | "nx": "19.3.0", 37 | "ts-node": "^10.9.2", 38 | "typescript": "^5.4.5", 39 | "typescript-eslint": "^7.13.0" 40 | }, 41 | "nx": {}, 42 | "dependencies": { 43 | "dotenv": "^16.4.5" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/check-can-publish.yml: -------------------------------------------------------------------------------- 1 | name: Check package can be published 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | env: 9 | # Check available versions below 10 | # https://github.com/actions/node-versions/blob/main/versions-manifest.json 11 | node-version: "20.x" 12 | 13 | jobs: 14 | check-can-publish: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | 21 | - name: Setup node 22 | uses: actions/setup-node@v3 23 | with: 24 | cache: "npm" 25 | node-version: ${{ env.node-version }} 26 | registry-url: "https://registry.npmjs.org" 27 | always-auth: true 28 | 29 | - name: Check if vwbl-core package version can be published 30 | uses: technote-space/package-version-check-action@v1 31 | with: 32 | PACKAGE_DIR: 'packages/core/' 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | PACKAGE_MANAGER: npm 35 | 36 | - name: Check if vwbl-sdk(evm chain) package version can be published 37 | uses: technote-space/package-version-check-action@v1 38 | with: 39 | PACKAGE_DIR: 'packages/evm/' 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | PACKAGE_MANAGER: npm 42 | 43 | - name: Check if vwbl-sdk-xrpl package version can be published 44 | uses: technote-space/package-version-check-action@v1 45 | with: 46 | PACKAGE_DIR: 'packages/xrpl/' 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | PACKAGE_MANAGER: npm 49 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/types/ConstructorPropsType.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { Web3 } from "web3"; 3 | 4 | import { 5 | IPFSConfig, 6 | AWSConfig, 7 | ManageKeyType, 8 | UploadContentType, 9 | UploadMetadataType 10 | } from "vwbl-core"; 11 | import { MetaTxConfig } from "./MetaTxConfigType"; 12 | 13 | export type BaseConstructorProps = { 14 | contractAddress: string; 15 | vwblNetworkUrl: string; 16 | uploadContentType?: UploadContentType; 17 | uploadMetadataType?: UploadMetadataType; 18 | awsConfig?: AWSConfig; 19 | ipfsConfig?: IPFSConfig; 20 | }; 21 | 22 | export type ConstructorProps = BaseConstructorProps & { 23 | web3: Web3; 24 | manageKeyType?: ManageKeyType; 25 | dataCollectorAddress?: string; 26 | }; 27 | 28 | export type VWBLOption = ConstructorProps; 29 | 30 | export type EthersConstructorProps = BaseConstructorProps & { 31 | ethersProvider: ethers.providers.BaseProvider; 32 | ethersSigner: ethers.Signer; 33 | manageKeyType?: ManageKeyType; 34 | dataCollectorAddress?: string; 35 | }; 36 | 37 | export type VWBLEthersOption = EthersConstructorProps; 38 | 39 | export type MetaTxConstructorProps = BaseConstructorProps & { 40 | bcProvider: ethers.providers.ExternalProvider | ethers.providers.JsonRpcFetchFunc | ethers.Wallet; 41 | metaTxConfig: MetaTxConfig; 42 | manageKeyType?: ManageKeyType; 43 | dataCollectorAddress?: string; 44 | }; 45 | 46 | export type VWBLMetaTxOption = MetaTxConstructorProps; 47 | 48 | export type ViewerConstructorProps = { 49 | provider: Web3 | ethers.providers.BaseProvider | ethers.Wallet; 50 | dataCollectorAddress: string; 51 | }; 52 | 53 | export type ViewerOption = ViewerConstructorProps; 54 | -------------------------------------------------------------------------------- /packages/core/src/vwbl/types/File.ts: -------------------------------------------------------------------------------- 1 | import * as Stream from "stream"; 2 | 3 | import { IPFSConfig } from "../../storage"; 4 | import { EncryptLogic } from "./EncryptLogic"; 5 | 6 | type UploadEncryptedFile = ( 7 | fileName: string, 8 | encryptedContent: string | Uint8Array | Stream.Readable, 9 | uuid: string 10 | ) => Promise; 11 | 12 | type UploadThumbnail = (thumbnailImage: FileOrPath, uuid: string) => Promise; 13 | 14 | type UploadMetadata = ( 15 | tokenId: string | number, 16 | name: string, 17 | description: string, 18 | previewImageUrl: string, 19 | encryptedDataUrl: string[], 20 | mimeType: string, 21 | encryptLogic: EncryptLogic 22 | ) => Promise; 23 | 24 | // type UploadEncryptedFileToIPFS = (encryptedContent: string | ArrayBuffer, ipfsConfig?: IPFSConfig) => Promise; 25 | type UploadEncryptedFileToIPFS = ( 26 | // encryptedContent: string | Uint8Array | Buffer, 27 | encryptedContent: string | Uint8Array | Stream.Readable, 28 | ipfsConfig?: IPFSConfig 29 | ) => Promise; 30 | 31 | type UploadThumbnailToIPFS = (thumbnailImage: FileOrPath, ipfsConfig?: IPFSConfig) => Promise; 32 | 33 | type UploadMetadataToIPFS = ( 34 | name: string, 35 | description: string, 36 | previewImageUrl: string, 37 | encryptedDataUrls: string[], 38 | mimeType: string, 39 | encryptLogic: EncryptLogic, 40 | ipfsConfig?: IPFSConfig 41 | ) => Promise; 42 | 43 | type FileOrPath = File | string; 44 | 45 | type Base64DataUrl = `data:${string};base64,${string}`; 46 | 47 | export { 48 | UploadMetadata, 49 | UploadEncryptedFile, 50 | UploadThumbnail, 51 | FileOrPath, 52 | UploadMetadataToIPFS, 53 | UploadEncryptedFileToIPFS, 54 | UploadThumbnailToIPFS, 55 | Base64DataUrl, 56 | }; 57 | -------------------------------------------------------------------------------- /packages/xrpl/src/api/XRPLApi.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { SubmittableTransaction } from "xrpl"; 3 | 4 | export class XRPLApi { 5 | private instance; 6 | constructor(endpointUrl: string) { 7 | this.instance = axios.create({ baseURL: endpointUrl }); 8 | } 9 | 10 | async setKey( 11 | documentId: string, 12 | xrplChainId: number, 13 | key: string, 14 | signature: string, 15 | publicKey: string, 16 | paymentTxHash?: string 17 | ) { 18 | await this.instance.post("/xrpl-keys", { 19 | document_id: documentId, 20 | xrpl_chain_id: xrplChainId, 21 | key, 22 | signature_tx_blob: signature, 23 | payment_tx_hash: paymentTxHash, 24 | pub_key: publicKey, 25 | }); 26 | } 27 | 28 | async getKey( 29 | signature: string, 30 | documentId: string, 31 | xrplChainId: number, 32 | pubKey: string 33 | ) { 34 | const response = await this.instance.get( 35 | `xrpl-keys/${documentId}/${xrplChainId}?signature=${signature}&pub_key=${pubKey}` 36 | ); 37 | 38 | return response.data.key; 39 | } 40 | 41 | async getXrplSignMessage( 42 | xrplChainId: number, 43 | address: string 44 | ): Promise { 45 | const response = await this.instance.get( 46 | `/xrpl-signature/${xrplChainId}?address=${address}` 47 | ); 48 | 49 | return response.data.signTx; 50 | } 51 | 52 | async getXrplPaymentInfo( 53 | tokenId: string, 54 | xrplChainId: number, 55 | walletAddress: string 56 | ) { 57 | const response = await this.instance.get( 58 | `/xrpl-payment-info/${tokenId}/${xrplChainId}?address=${walletAddress}` 59 | ); 60 | 61 | return { 62 | mintFee: response.data.mintFee, 63 | destination: response.data.destination, 64 | }; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/core/src/util/fileHelper.ts: -------------------------------------------------------------------------------- 1 | import mime from "mime-types"; 2 | import path from "path"; 3 | 4 | import { Base64DataUrl, FileOrPath } from "../vwbl"; 5 | import { isRunningOnBrowser } from "./envUtil"; 6 | 7 | export const toBase64FromFile = async (file: File): Promise => { 8 | if (isRunningOnBrowser()) { 9 | return new Promise((resolve, reject) => { 10 | const reader = new window.FileReader(); 11 | reader.readAsDataURL(file); 12 | reader.onload = () => { 13 | const result = reader.result; 14 | if (!result || typeof result !== "string") { 15 | reject("cannot convert to base64 string"); 16 | } else { 17 | resolve(result as Base64DataUrl); 18 | } 19 | }; 20 | }); 21 | } else { 22 | return new Promise((resolve, reject) => { 23 | try { 24 | const arrayBuffer = file.arrayBuffer(); 25 | arrayBuffer 26 | .then((buffer) => { 27 | const base64 = Buffer.from(buffer).toString("base64"); 28 | const mimetype = getMimeType(file.name); 29 | const dataUrl: Base64DataUrl = `data:${mimetype};base64,${base64}`; 30 | resolve(dataUrl); 31 | }) 32 | .catch(reject); 33 | } catch (error) { 34 | reject(error); 35 | } 36 | }); 37 | } 38 | }; 39 | 40 | export const getMimeType = (file: FileOrPath): string => { 41 | if (typeof file === "string") { 42 | const fileExtension = path.extname(file); 43 | return mime.lookup(fileExtension) || ""; 44 | } else { 45 | return file.type || ""; 46 | } 47 | }; 48 | 49 | export const toArrayBuffer = async (blob: Blob): Promise => { 50 | if (isRunningOnBrowser()) { 51 | return new Promise((resolve, reject) => { 52 | const reader = new window.FileReader(); 53 | reader.readAsArrayBuffer(blob); 54 | reader.onload = () => { 55 | const result = reader.result; 56 | if (!result || !(result instanceof Uint8Array)) { 57 | reject("cannot convert to ArrayBuffer"); 58 | } else { 59 | resolve(result); 60 | } 61 | }; 62 | reader.onerror = (error: any) => reject(error); // eslint-disable-line @typescript-eslint/no-explicit-any 63 | }); 64 | } 65 | return await blob.arrayBuffer(); 66 | }; 67 | -------------------------------------------------------------------------------- /packages/evm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vwbl-sdk", 3 | "description": "VWBL SDK for EVM Compatible Chain", 4 | "version": "0.1.21", 5 | "main": "dist/packages/evm/src/index.js", 6 | "types": "dist/packages/evm/src/index.d.ts", 7 | "engines": { 8 | "node": ">=20.0.0" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "license": "MIT", 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/VWBL/VWBL-SDK.git" 20 | }, 21 | "scripts": { 22 | "build": "tsc", 23 | "format": "eslint --fix --quiet './src/**/*.ts*'", 24 | "lint": "eslint './src/**/*.ts*'", 25 | "test": "NODE_OPTIONS=--experimental-vm-modules jest", 26 | "mocha": "mocha-parallel-tests -r ts-node/register --exit" 27 | }, 28 | "devDependencies": { 29 | "@babel/preset-env": "^7.24.5", 30 | "@babel/preset-typescript": "^7.23.3", 31 | "@truffle/hdwallet-provider": "^2.1.15", 32 | "@types/chai": "^4.3.0", 33 | "@types/crypto-js": "^4.0.2", 34 | "@types/ethereumjs-abi": "^0.6.5", 35 | "@types/jest": "^29.5.12", 36 | "@types/mime-types": "^2.1.4", 37 | "@types/mocha": "^9.1.0", 38 | "@types/sinon": "^17.0.3", 39 | "@types/uuid": "^8.3.3", 40 | "chai": "^4.3.6", 41 | "dotenv": "^16.0.3", 42 | "file-api": "^0.10.4", 43 | "jest": "^29.7.0", 44 | "mocha": "^7.1.2", 45 | "mocha-parallel-tests": "^2.3.0", 46 | "prettier": "^2.5.1", 47 | "sinon": "^17.0.1", 48 | "ts-jest": "^29.1.2", 49 | "ts-loader": "^9.5.1", 50 | "ts-node": "^10.9.2", 51 | "typescript": "^5.3.3" 52 | }, 53 | "dependencies": { 54 | "@aws-sdk/client-s3": "^3.338.0", 55 | "@aws-sdk/credential-providers": "^3.338.0", 56 | "@aws-sdk/lib-storage": "^3.338.0", 57 | "vwbl-core": "^0.1.0", 58 | "axios": "^0.27.2", 59 | "crypto": "^1.0.1", 60 | "crypto-js": "^4.1.1", 61 | "ethereumjs-abi": "^0.6.8", 62 | "ethers": "5.7.2", 63 | "fetch-blob": "^4.0.0", 64 | "form-data": "^4.0.0", 65 | "jimp": "^0.16.1", 66 | "mime-types": "^2.1.35", 67 | "uuid": "^8.3.2", 68 | "web3": "^4.3.0" 69 | }, 70 | "browser": { 71 | "crypto": false, 72 | "filereader": false, 73 | "fs": false, 74 | "os": false, 75 | "path": false 76 | }, 77 | "react-native": { 78 | "fs": false, 79 | "path": false, 80 | "os": false, 81 | "filereader": false, 82 | "crypto": false 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/core/src/util/cryptoHelper.ts: -------------------------------------------------------------------------------- 1 | import nodeCrypto from "crypto"; 2 | import crypto from "crypto-js"; 3 | import * as Stream from "stream"; 4 | import * as uuid from "uuid"; 5 | 6 | import { toArrayBuffer } from "./fileHelper"; 7 | 8 | export const createRandomKey = uuid.v4; 9 | export const encryptString = (message: string, key: string) => { 10 | return crypto.AES.encrypt(message, key).toString(); 11 | }; 12 | 13 | export const decryptString = (cipherText: string, key: string) => { 14 | return crypto.AES.decrypt(cipherText, key).toString(crypto.enc.Utf8); 15 | }; 16 | 17 | export const encryptFileOnBrowser = async (file: File, key: string): Promise => { 18 | const crypto = window.crypto; 19 | const subtle = crypto.subtle; 20 | const aes = { 21 | name: "AES-CBC", 22 | iv: new Uint8Array(16), 23 | }; 24 | const aesAlgorithmKeyGen = { 25 | name: "AES-CBC", 26 | length: 256, 27 | }; 28 | // UUID v4から-をとるとちょうど32文字 = 256bite 29 | const keyData = new TextEncoder().encode(key.replace(/-/g, "")); 30 | const aesKey = await subtle.importKey("raw", keyData, aesAlgorithmKeyGen, true, ["encrypt"]); 31 | return new Uint8Array(await subtle.encrypt(aes, aesKey, await file.arrayBuffer())); 32 | }; 33 | 34 | export const decryptFileOnBrowser = async (encryptedFile: ArrayBuffer, key: string): Promise => { 35 | const crypto = window.crypto; 36 | const subtle = crypto.subtle; 37 | const aes = { 38 | name: "AES-CBC", 39 | iv: new Uint8Array(16), 40 | }; 41 | const aesAlgorithmKeyGen = { 42 | name: "AES-CBC", 43 | length: 256, 44 | }; 45 | const keyData = new TextEncoder().encode(key.replace(/-/g, "")); 46 | const aesKey = await subtle.importKey("raw", keyData, aesAlgorithmKeyGen, true, ["decrypt"]); 47 | return await subtle.decrypt(aes, aesKey, encryptedFile); 48 | }; 49 | 50 | export const encryptFileOnNode = async (file: File, key: string): Promise => { 51 | const keyData = new TextEncoder().encode(key.replace(/-/g, "")); 52 | const cipher = nodeCrypto.createCipheriv("aes-256-cbc", keyData, new Uint8Array(16)); 53 | const start = cipher.update(Buffer.from(await toArrayBuffer(file))); 54 | const final = cipher.final(); 55 | return new Uint8Array(Buffer.concat([start, final]).buffer); 56 | }; 57 | 58 | export const decryptFileOnNode = (encryptedFile: ArrayBuffer, key: string): ArrayBuffer => { 59 | const keyData = new TextEncoder().encode(key.replace(/-/g, "")); 60 | const decipher = nodeCrypto.createDecipheriv("aes-256-cbc", keyData, new Uint8Array(16)); 61 | const start = decipher.update(Buffer.from(encryptedFile)); 62 | const final = decipher.final(); 63 | return Buffer.concat([start, final]).buffer; 64 | }; 65 | 66 | export const encryptStream = (stream: Stream, key: string): Stream.Readable => { 67 | const keyData = new TextEncoder().encode(key.replace(/-/g, "")); 68 | const cipher = nodeCrypto.createCipheriv("aes-256-cbc", keyData, new Uint8Array(16)); 69 | return stream.pipe(cipher); 70 | }; 71 | 72 | export const decryptStream = (stream: Stream, key: string): Stream => { 73 | const keyData = new TextEncoder().encode(key.replace(/-/g, "")); 74 | const decipher = nodeCrypto.createDecipheriv("aes-256-cbc", keyData, new Uint8Array(16)); 75 | return stream.pipe(decipher); 76 | }; 77 | 78 | export const encryptFile = typeof window === "undefined" ? encryptFileOnNode : encryptFileOnBrowser; 79 | export const decryptFile = typeof window === "undefined" ? decryptFileOnNode : decryptFileOnBrowser; 80 | -------------------------------------------------------------------------------- /packages/evm/src/util/biconomyHelper.ts: -------------------------------------------------------------------------------- 1 | import * as abi from "ethereumjs-abi"; 2 | import { ethers } from "ethers"; 3 | 4 | const biconomyForwarderDomainData = { 5 | name: "Biconomy Forwarder", 6 | version: "1", 7 | verifyingContract: "", 8 | salt: "", 9 | }; 10 | 11 | const domainType = [ 12 | { name: "name", type: "string" }, 13 | { name: "version", type: "string" }, 14 | { name: "verifyingContract", type: "address" }, 15 | { name: "salt", type: "bytes32" }, 16 | ]; 17 | const forwardRequestType = [ 18 | { name: "from", type: "address" }, 19 | { name: "to", type: "address" }, 20 | { name: "token", type: "address" }, 21 | { name: "txGas", type: "uint256" }, 22 | { name: "tokenGasPrice", type: "uint256" }, 23 | { name: "batchId", type: "uint256" }, 24 | { name: "batchNonce", type: "uint256" }, 25 | { name: "deadline", type: "uint256" }, 26 | { name: "data", type: "bytes" }, 27 | ]; 28 | 29 | export interface TxParam { 30 | from: string; 31 | to: string; 32 | token: string; 33 | txGas: number; 34 | tokenGasPrice: string; 35 | batchId: number; 36 | batchNonce: number; 37 | deadline: number; 38 | data: string; 39 | } 40 | 41 | export const buildForwardTxRequest = ( 42 | account: string, 43 | toAddress: string, 44 | gasLimitNum: number, 45 | batchNonce: string, 46 | data: string 47 | ) => { 48 | const req: TxParam = { 49 | from: account, 50 | to: toAddress, 51 | token: "0x0000000000000000000000000000000000000000", 52 | txGas: gasLimitNum, 53 | tokenGasPrice: "0", 54 | batchId: parseInt("0"), 55 | batchNonce: parseInt(batchNonce), 56 | deadline: Math.floor(Date.now() / 1000 + 3600), 57 | data, 58 | }; 59 | return req; 60 | }; 61 | 62 | export const getDataToSignForEIP712 = (request: TxParam, forwarderAddress: string, chainId: number) => { 63 | const domainData = biconomyForwarderDomainData; 64 | domainData.verifyingContract = forwarderAddress; 65 | domainData.salt = ethers.utils.hexZeroPad(ethers.BigNumber.from(chainId).toHexString(), 32); 66 | const dataToSign = JSON.stringify({ 67 | types: { 68 | EIP712Domain: domainType, 69 | ERC20ForwardRequest: forwardRequestType, 70 | }, 71 | domain: domainData, 72 | primaryType: "ERC20ForwardRequest", 73 | message: request, 74 | }); 75 | return dataToSign; 76 | }; 77 | 78 | export const getDomainSeparator = (forwarderAddress: string, chainId: number) => { 79 | const domainSeparator = ethers.utils.keccak256( 80 | ethers.utils.defaultAbiCoder.encode( 81 | ["bytes32", "bytes32", "bytes32", "address", "bytes32"], 82 | [ 83 | ethers.utils.id("EIP712Domain(string name,string version,address verifyingContract,bytes32 salt)"), 84 | ethers.utils.id(biconomyForwarderDomainData.name), 85 | ethers.utils.id(biconomyForwarderDomainData.version), 86 | forwarderAddress, 87 | ethers.utils.hexZeroPad(ethers.BigNumber.from(chainId).toHexString(), 32), 88 | ] 89 | ) 90 | ); 91 | return domainSeparator; 92 | }; 93 | 94 | export const getDataToSignForPersonalSign = (request: TxParam) => { 95 | const hashToSign = abi.soliditySHA3( 96 | ["address", "address", "address", "uint256", "uint256", "uint256", "uint256", "uint256", "bytes32"], 97 | [ 98 | request.from, 99 | request.to, 100 | request.token, 101 | request.txGas, 102 | request.tokenGasPrice, 103 | request.batchId, 104 | request.batchNonce, 105 | request.deadline, 106 | ethers.utils.keccak256(request.data), 107 | ] 108 | ); 109 | 110 | return hashToSign; 111 | }; 112 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/base.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { Web3 } from "web3"; 3 | 4 | import { VWBLApi } from "./api"; 5 | import { signToProtocol } from "./blockchain"; 6 | import { BaseConstructorProps } from "./types"; 7 | import { CoreConstructorProps, VWBLCore } from "vwbl-core"; 8 | const MESSAGE_TO_BE_SIGNED = "Hello VWBL"; 9 | 10 | export class VWBLBase extends VWBLCore { 11 | protected api: VWBLApi; 12 | public signMsg?: string; 13 | public signature?: string; 14 | public contractAddress: string; 15 | 16 | // eslint-disable-next-line @typescript-eslint/no-empty-function 17 | constructor(props: BaseConstructorProps) { 18 | super(props as CoreConstructorProps); 19 | 20 | const { contractAddress, vwblNetworkUrl } = props; 21 | this.contractAddress = contractAddress; 22 | this.api = new VWBLApi(vwblNetworkUrl); 23 | } 24 | 25 | /** 26 | * Sign to VWBL 27 | * 28 | * @remarks 29 | * You need to call this method before you send a transaction(eg. mint NFT, decrypt NFT data) 30 | */ 31 | protected _sign = async (signer: Web3 | ethers.Signer, targetContract?: string) => { 32 | //TODO: signerがWeb3 instanceかどうかを判断するロジックを切り出さないといけない signer instanceof Web3では意図した通り動かなかったため 33 | const castedSigner = signer as any; // eslint-disable-line @typescript-eslint/no-explicit-any 34 | // eslint-disable-next-line 35 | const chainId = castedSigner.hasOwnProperty("eth") 36 | ? await castedSigner.eth.getChainId() 37 | : await castedSigner.getChainId(); 38 | const address = await this._getAddressBySigner(signer); 39 | const contractAddress = targetContract || this.contractAddress; 40 | const signMessage = await this.api 41 | .getSignMessage(contractAddress, chainId, address) 42 | .catch(() => MESSAGE_TO_BE_SIGNED); 43 | if (this.signMsg === signMessage) return; 44 | this.signMsg = signMessage; 45 | this.signature = await signToProtocol(signer, signMessage); 46 | console.log("signed"); 47 | }; 48 | 49 | protected _getAddressBySigner = async (signer: Web3 | ethers.Signer): Promise => { 50 | //TODO: signerがWeb3 instanceかどうかを判断するロジックを切り出さないといけない signer instanceof Web3では意図した通り動かなかったため 51 | const castedSigner = signer as any; // eslint-disable-line @typescript-eslint/no-explicit-any 52 | // eslint-disable-next-line 53 | return castedSigner.hasOwnProperty("eth") 54 | ? (await castedSigner.eth.getAccounts())[0] 55 | : await castedSigner.getAddress(); 56 | }; 57 | 58 | /** 59 | * Set key to VWBL Network 60 | * 61 | * @param documentId - DocumentId 62 | * @param chainId - The indentifier of blockchain 63 | * @param key - The key generated by {@link VWBL.createKey} 64 | * @param address address 65 | * @param hasNonce 66 | * @param autoMigration 67 | * 68 | */ 69 | protected _setKey = async ( 70 | documentId: string, 71 | chainId: number, 72 | key: string, 73 | address?: string, 74 | hasNonce?: boolean, // eslint-disable-line @typescript-eslint/no-unused-vars 75 | autoMigration?: boolean // eslint-disable-line @typescript-eslint/no-unused-vars 76 | ): Promise => { 77 | if (!this.signature) { 78 | throw "please sign first"; 79 | } 80 | await this.api.setKey(documentId, chainId, key, this.signature, address); 81 | }; 82 | 83 | /** 84 | * Set key to VWBL Network 85 | * 86 | * @param documentId - DocumentId 87 | * @param chainId - The indentifier of blockchain 88 | * @param address address 89 | * 90 | */ 91 | protected _getKey = async (documentId: string, chainId: number, address?: string): Promise => { 92 | if (!this.signature) { 93 | throw "please sign first"; 94 | } 95 | return await this.api.getKey(documentId, chainId, this.signature, address); 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /packages/xrpl/src/blockchain/VWBLProtocol.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AccountNFToken, 3 | Client, 4 | Request, 5 | SubmittableTransaction, 6 | } from "xrpl"; 7 | import { NFTokenMintMetadata } from "xrpl/dist/npm/models/transactions/NFTokenMint"; 8 | 9 | export class VWBLXRPLProtocol { 10 | private client: Client; 11 | 12 | constructor(xrplChainId: number) { 13 | let publicServerUrl: string; 14 | switch (xrplChainId) { 15 | case 0: 16 | publicServerUrl = "wss://s2.ripple.com"; 17 | break; 18 | case 1: 19 | publicServerUrl = "wss://s.altnet.rippletest.net:51233"; 20 | break; 21 | case 2: 22 | publicServerUrl = "wss://s.devnet.rippletest.net:51233"; 23 | break; 24 | default: 25 | throw new Error("Invalid xrplChainId"); 26 | } 27 | const client = new Client(publicServerUrl); 28 | this.client = client; 29 | } 30 | 31 | async mint(signedMintTx: string) { 32 | await this.client.connect(); 33 | 34 | let tokenId: string; 35 | try { 36 | const response = await this.client.submitAndWait(signedMintTx); 37 | 38 | const txMetadata = response.result.meta as NFTokenMintMetadata; 39 | if (txMetadata.nftoken_id) { 40 | tokenId = txMetadata.nftoken_id; 41 | } else { 42 | throw Error("nftoken_id is empty"); 43 | } 44 | } catch (e) { 45 | throw Error(`failed to submit NFTokenMint tx: ${e}`); 46 | } finally { 47 | await this.client.disconnect(); 48 | } 49 | 50 | return tokenId; 51 | } 52 | 53 | async generatePaymentTx( 54 | tokenId: string, 55 | senderAddress: string, 56 | mintFee: string, 57 | destinationAddress: string 58 | ) { 59 | await this.client.connect(); 60 | 61 | const fee = await this.client.request({ 62 | command: "fee", 63 | }); 64 | const drops = fee.result.drops; 65 | 66 | const accountInfo = await this.client.request({ 67 | command: "account_info", 68 | account: senderAddress, 69 | }); 70 | const sequence = accountInfo.result.account_data.Sequence; 71 | 72 | const ledger = await this.client.request({ 73 | command: "ledger", 74 | ledger_index: "validated", 75 | }); 76 | const currentLedgerIndex = ledger.result.ledger_index; 77 | await this.client.disconnect(); 78 | 79 | const paymentTxJson: SubmittableTransaction = { 80 | TransactionType: "Payment", 81 | Account: senderAddress, 82 | Destination: destinationAddress, 83 | Amount: mintFee, 84 | Fee: drops.minimum_fee, 85 | LastLedgerSequence: currentLedgerIndex + 4, 86 | Sequence: sequence, 87 | Memos: [ 88 | { 89 | Memo: { 90 | MemoData: tokenId, 91 | }, 92 | }, 93 | ], 94 | }; 95 | 96 | return paymentTxJson; 97 | } 98 | 99 | async payMintFee(signedPaymentTx: string) { 100 | await this.client.connect(); 101 | 102 | try { 103 | const response = await this.client.submitAndWait(signedPaymentTx); 104 | if (response.result.hash) { 105 | const memos = response.result.Memos; 106 | if (memos && memos[0].Memo.MemoData) { 107 | return { 108 | paymentTxHash: response.result.hash, 109 | tokenId: memos[0].Memo.MemoData, 110 | }; 111 | } 112 | } 113 | } catch (e) { 114 | throw Error(`failed to submit Payment tx ${e}`); 115 | } finally { 116 | await this.client.disconnect(); 117 | } 118 | } 119 | 120 | async fetchNFTInfo( 121 | address: string, 122 | nftokenId: string 123 | ): Promise { 124 | await this.client.connect(); 125 | const request: Request = { 126 | command: "account_nfts", 127 | account: address, 128 | }; 129 | 130 | const accountNFTs = await this.client.request(request); 131 | const nftInfo = accountNFTs.result.account_nfts.find( 132 | (nft: AccountNFToken) => nft.NFTokenID === nftokenId 133 | ); 134 | 135 | if (!nftInfo) { 136 | throw new Error(`NFT of ID: ${nftokenId} not found`); 137 | } 138 | await this.client.disconnect(); 139 | 140 | return nftInfo; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /.nx/nxw.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // This file should be committed to your repository! It wraps Nx and ensures 3 | // that your local installation matches nx.json. 4 | // See: https://nx.dev/recipes/installation/install-non-javascript for more info. 5 | 6 | 7 | 8 | 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | const fs = require('fs'); 11 | const path = require('path'); 12 | const cp = require('child_process'); 13 | const installationPath = path.join(__dirname, 'installation', 'package.json'); 14 | function matchesCurrentNxInstall(currentInstallation, nxJsonInstallation) { 15 | if (!currentInstallation.devDependencies || 16 | !Object.keys(currentInstallation.devDependencies).length) { 17 | return false; 18 | } 19 | try { 20 | if (currentInstallation.devDependencies['nx'] !== 21 | nxJsonInstallation.version || 22 | require(path.join(path.dirname(installationPath), 'node_modules', 'nx', 'package.json')).version !== nxJsonInstallation.version) { 23 | return false; 24 | } 25 | for (const [plugin, desiredVersion] of Object.entries(nxJsonInstallation.plugins || {})) { 26 | if (currentInstallation.devDependencies[plugin] !== desiredVersion) { 27 | return false; 28 | } 29 | } 30 | return true; 31 | } 32 | catch { 33 | return false; 34 | } 35 | } 36 | function ensureDir(p) { 37 | if (!fs.existsSync(p)) { 38 | fs.mkdirSync(p, { recursive: true }); 39 | } 40 | } 41 | function getCurrentInstallation() { 42 | try { 43 | return require(installationPath); 44 | } 45 | catch { 46 | return { 47 | name: 'nx-installation', 48 | version: '0.0.0', 49 | devDependencies: {}, 50 | }; 51 | } 52 | } 53 | function performInstallation(currentInstallation, nxJson) { 54 | fs.writeFileSync(installationPath, JSON.stringify({ 55 | name: 'nx-installation', 56 | devDependencies: { 57 | nx: nxJson.installation.version, 58 | ...nxJson.installation.plugins, 59 | }, 60 | })); 61 | try { 62 | cp.execSync('npm i', { 63 | cwd: path.dirname(installationPath), 64 | stdio: 'inherit', 65 | }); 66 | } 67 | catch (e) { 68 | // revert possible changes to the current installation 69 | fs.writeFileSync(installationPath, JSON.stringify(currentInstallation)); 70 | // rethrow 71 | throw e; 72 | } 73 | } 74 | function ensureUpToDateInstallation() { 75 | const nxJsonPath = path.join(__dirname, '..', 'nx.json'); 76 | let nxJson; 77 | try { 78 | nxJson = require(nxJsonPath); 79 | if (!nxJson.installation) { 80 | console.error('[NX]: The "installation" entry in the "nx.json" file is required when running the nx wrapper. See https://nx.dev/recipes/installation/install-non-javascript'); 81 | process.exit(1); 82 | } 83 | } 84 | catch { 85 | console.error('[NX]: The "nx.json" file is required when running the nx wrapper. See https://nx.dev/recipes/installation/install-non-javascript'); 86 | process.exit(1); 87 | } 88 | try { 89 | ensureDir(path.join(__dirname, 'installation')); 90 | const currentInstallation = getCurrentInstallation(); 91 | if (!matchesCurrentNxInstall(currentInstallation, nxJson.installation)) { 92 | performInstallation(currentInstallation, nxJson); 93 | } 94 | } 95 | catch (e) { 96 | const messageLines = [ 97 | '[NX]: Nx wrapper failed to synchronize installation.', 98 | ]; 99 | if (e instanceof Error) { 100 | messageLines.push(''); 101 | messageLines.push(e.message); 102 | messageLines.push(e.stack); 103 | } 104 | else { 105 | messageLines.push(e.toString()); 106 | } 107 | console.error(messageLines.join('\n')); 108 | process.exit(1); 109 | } 110 | } 111 | if (!process.env.NX_WRAPPER_SKIP_INSTALL) { 112 | ensureUpToDateInstallation(); 113 | } 114 | 115 | require('./installation/node_modules/nx/bin/nx'); 116 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/blockchain/erc6150/VWBLMetaTxProtocol.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | import vwblERC6150 from "../../../contract/VWBLERC6150ERC2981.json"; 4 | import vwblERC6150Ipfs from "../../../contract/VWBLERC6150ERC2981ForMetadata.json"; 5 | import { GrantViewPermissionTxParam, MintForIPFSTxParam, MintTxParam } from "../../types"; 6 | import { VWBLNFTMetaTx } from "../erc721/VWBLMetaTxProtocol"; 7 | import { parseToTokenId } from "../erc721/VWBLProtocolEthers"; 8 | 9 | export class VWBLERC6150MetaTxEthers extends VWBLNFTMetaTx { 10 | private erc6150Address: string; 11 | 12 | constructor( 13 | walletProvider: ethers.providers.Web3Provider | ethers.Wallet, 14 | address: string, 15 | forwarderAddress: string, 16 | metaTxEndpoint: string 17 | ) { 18 | super(walletProvider, address, forwarderAddress, metaTxEndpoint); 19 | this.erc6150Address = address; 20 | } 21 | 22 | override async mintToken(mintParam: MintTxParam): Promise { 23 | const myAddress = await this.ethersSigner.getAddress(); 24 | const vwblMetaTxContract = new ethers.Contract(this.erc6150Address, vwblERC6150.abi, this.ethersSigner); 25 | const { data } = await vwblMetaTxContract.populateTransaction.mint( 26 | mintParam.decryptUrl, 27 | mintParam.parentId, 28 | mintParam.feeNumerator, 29 | mintParam.documentId 30 | ); 31 | const chainId = await this.ethersSigner.getChainId(); 32 | const { txParam, sig, domainSeparator } = await this.constructMetaTx(myAddress, data!, chainId); 33 | console.log("transaction start"); 34 | const receipt = await this.sendTransaction( 35 | txParam, 36 | sig, 37 | myAddress, 38 | domainSeparator 39 | ); 40 | console.log("transaction end"); 41 | const tokenId = parseToTokenId(receipt); 42 | return tokenId; 43 | } 44 | 45 | override async mintTokenForIPFS(mintForIPFSParam: MintForIPFSTxParam): Promise { 46 | const myAddress = await this.ethersSigner.getAddress(); 47 | const vwblMetaTxContract = new ethers.Contract(this.erc6150Address, vwblERC6150Ipfs.abi, this.ethersSigner); 48 | const { data } = await vwblMetaTxContract.populateTransaction.mint( 49 | mintForIPFSParam.metadataUrl, 50 | mintForIPFSParam.parentId, 51 | mintForIPFSParam.decryptUrl, 52 | mintForIPFSParam.feeNumerator, 53 | mintForIPFSParam.documentId 54 | ); 55 | const chainId = await this.ethersSigner.getChainId(); 56 | const { txParam, sig, domainSeparator } = await this.constructMetaTx(myAddress, data!, chainId); 57 | console.log("transaction start"); 58 | const receipt = await this.sendTransaction( 59 | txParam, 60 | sig, 61 | myAddress, 62 | domainSeparator 63 | ); 64 | console.log("transaction end"); 65 | const tokenId = parseToTokenId(receipt); 66 | return tokenId; 67 | } 68 | 69 | override async grantViewPermission(grantParam: GrantViewPermissionTxParam): Promise { 70 | const myAddress = await this.ethersSigner.getAddress(); 71 | const vwblMetaTxContract = new ethers.Contract(this.erc6150Address, vwblERC6150Ipfs.abi, this.ethersSigner); 72 | const { data } = await vwblMetaTxContract.populateTransaction.grantViewPermission( 73 | grantParam.tokenId, 74 | grantParam.grantee, 75 | grantParam.toDir 76 | ); 77 | const chainId = await this.ethersSigner.getChainId(); 78 | const { txParam, sig, domainSeparator } = await this.constructMetaTx(myAddress, data!, chainId); 79 | console.log("transaction start"); 80 | await this.sendTransaction( 81 | txParam, 82 | sig, 83 | myAddress, 84 | domainSeparator 85 | ); 86 | console.log("transaction end"); 87 | } 88 | 89 | async revokeDirPermission(tokenId: number, revoker: string): Promise { 90 | const myAddress = await this.ethersSigner.getAddress(); 91 | const vwblMetaTxContract = new ethers.Contract(this.erc6150Address, vwblERC6150Ipfs.abi, this.ethersSigner); 92 | const { data } = await vwblMetaTxContract.populateTransaction.revokeDirPermission(tokenId, revoker); 93 | const chainId = await this.ethersSigner.getChainId(); 94 | const { txParam, sig, domainSeparator } = await this.constructMetaTx(myAddress, data!, chainId); 95 | console.log("transaction start"); 96 | await this.sendTransaction(txParam, sig, myAddress, domainSeparator); 97 | console.log("transaction end"); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /packages/xrpl/README.md: -------------------------------------------------------------------------------- 1 | # VWBL SDK for XRPL 2 | 3 | ## official document 4 | 5 | https://docs.vwbl-protocol.org 6 | 7 | ### install 8 | 9 | ##### Using NPM 10 | 11 | `npm install vwbl-sdk-xrpl` 12 | 13 | ##### Using Yarn 14 | 15 | `yarn add vwbl-sdk-xrpl` 16 | 17 | ### Build 18 | 19 | `npm run build:xrpl` 20 | 21 | ### Test 22 | 23 | `npm run test:xrpl` 24 | 25 | ## api document 26 | 27 | ### create instance 28 | 29 | ```typescript 30 | const vwblXrpl = new VWBLXRPL({ 31 | xrplChainId: 0, // Mainnet:0 / Testnet:1 / Devnet:2 32 | vwblNetworkUrl: "https://vwbl.network", 33 | uploadContentType: UploadContentType.S3, 34 | uploadMetadataType: UploadMetadataType.S3, 35 | awsConfig: { 36 | region: "ap-northeast-1", 37 | idPoolId: "ap-northeast-1:...", 38 | cloudFrontUrl: "https://xxx.cloudfront.net", 39 | bucketName: { 40 | metadata: "vwbl-metadata", 41 | content: "vwbl-content", 42 | }, 43 | }, 44 | }); 45 | ``` 46 | 47 | Constructor Options 48 | 49 | | name | required | type | description | 50 | | ------------------ | -------------------------------------------------------- | ------------------ | -------------------------------------------------------------------------- | 51 | | xrplChainId | true | number | XRPL public server type | 52 | | vwblNetworkUrl | true | string | VWBL network's url | 53 | | uploadContentType | flase | UploadContentType | where to upload content, you can choose from
S3
IPFS
CUSTOM | 54 | | uploadMetadataType | flase | UploadMetadataType | where to upload content, you can choose from
S3
IPFS
CUSTOM | 55 | | awsConfig | true if you choose to upload content or metadata to S3 | AWSConfig | AWSConfig \*1 | 56 | | ipfsConfig | true if you choose to upload content or metadata to IPFS | IPFSConfig | IPFSConfig \*2 | 57 | 58 | AWSConfig(\*1) 59 | 60 | | name | required | type | description | 61 | | ------------- | -------- | ----------------------------------- | --------------------------------------------------------- | 62 | | region | true | string | AWS region | 63 | | idPoolId | true | string | idPoolId which has granted S3-put-object | 64 | | cloudFrontUrl | true | string | cloudFront url connect to s3 which is uploaded content | 65 | | bucketName | true | {content: string, metadata: string} | bucketName of metadata and content, it's ok they are same | 66 | 67 | IPFSConfig(\*2) 68 | 69 | | name | required | type | description | 70 | | --------- | -------- | ------ | -------------- | 71 | | apiKey | true | string | API key | 72 | | apiSecret | false | string | API secret key | 73 | 74 | | 75 | 76 | ## SDK 77 | 78 | ![SDK Flow](src/public/sdk-flow.png) 79 | 80 | ### Generate NFT mint tx object to get user's signature 81 | 82 | ```typescript 83 | await vwblXrpl.generateMintTokenTx( 84 | walletAddress, // user's wallet address 85 | transferRoyalty, 86 | isTransferable, 87 | isBurnable 88 | ); 89 | ``` 90 | 91 | ### mint VWBL NFT 92 | 93 | ```typescript 94 | await vwblXrpl.mintAndGeneratePaymentTx( 95 | signedMintTx, // signed tx of `generateMintTokenTx` 96 | walletAddress 97 | ); 98 | ``` 99 | 100 | ### pay mint fee 101 | 102 | ```typescript 103 | await vwblXrpl.payMintFee( 104 | walletAddress, 105 | signedPaymentTx // signed tx of `mintAndGeneratePaymentTx` 106 | ); 107 | ``` 108 | 109 | ### create token 110 | 111 | ```typescript 112 | await vwbl.managedCreateToken( 113 | tokenId, 114 | xrplChainId, 115 | signedEmptyTx, 116 | signedPaymentTxHash, 117 | signerPublicKey, 118 | name, 119 | description, 120 | plainFile, 121 | thumbnailImage, 122 | encryptLogic="base64", 123 | uploadEncryptedFileCallback?, 124 | uploadThumbnailCallback?, 125 | uploadMetadataCallBack? 126 | ); 127 | ``` 128 | 129 | ### view contents ( get NFT metadata from given tokenId) 130 | 131 | ```typescript 132 | // generate tx object to get user's signature 133 | const emptyTxObject = await vwblXrpl.generateTxForSigning(walletAddress); 134 | 135 | // decrypt & fetch metadata 136 | const { 137 | id, 138 | name, 139 | description, 140 | image, 141 | mimeType, 142 | encryptLogic, 143 | ownDataBase64, 144 | ownFiles, 145 | fileName, 146 | } = vwblXrpl.extractMetadata( 147 | tokenId, 148 | signedEmptyTx, 149 | signerPublicKey 150 | ) 151 | ``` 152 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/blockchain/erc6150/VWBLProtocolEthers.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | import vwblERC6150 from "../../../contract/VWBLERC6150ERC2981.json"; 4 | import vwblERC6150IPFS from "../../../contract/VWBLERC6150ERC2981ForMetadata.json"; 5 | import { getFeeSettingsBasedOnEnvironment } from "../../../util/transactionHelper"; 6 | import { GasSettings, GrantViewPermissionTxParam, MintForIPFSTxParam, MintTxParam } from "../../types"; 7 | import { parseToTokenId, VWBLNFTEthers } from "../erc721/VWBLProtocolEthers"; 8 | 9 | export class VWBLERC6150Ethers extends VWBLNFTEthers { 10 | private erc6150Contract: ethers.Contract; 11 | 12 | constructor( 13 | address: string, 14 | isIpfs: boolean, 15 | ethersProvider: ethers.providers.BaseProvider, 16 | ethersSigner: ethers.Signer 17 | ) { 18 | super(address, isIpfs, ethersProvider, ethersSigner); 19 | this.erc6150Contract = isIpfs 20 | ? new ethers.Contract(address, vwblERC6150IPFS.abi, ethersSigner) 21 | : new ethers.Contract(address, vwblERC6150.abi, ethersSigner); 22 | } 23 | 24 | override async mintToken(mintParam: MintTxParam): Promise { 25 | const fee = await this.getFee(); 26 | let txSettings: unknown; 27 | if (mintParam.gasSettings?.gasPrice) { 28 | txSettings = { 29 | value: fee, 30 | gasPrice: mintParam.gasSettings?.gasPrice, 31 | }; 32 | } else { 33 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 34 | getFeeSettingsBasedOnEnvironment( 35 | mintParam.gasSettings?.maxPriorityFeePerGas, 36 | mintParam.gasSettings?.maxFeePerGas 37 | ); 38 | txSettings = { 39 | value: fee, 40 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 41 | maxFeePerGas: _maxFeePerGas, 42 | }; 43 | } 44 | console.log("transaction start"); 45 | const tx = await this.erc6150Contract.mint( 46 | mintParam.decryptUrl, 47 | mintParam.parentId, 48 | mintParam.feeNumerator, 49 | mintParam.documentId, 50 | txSettings 51 | ); 52 | const receipt = await this.ethersProvider.waitForTransaction(tx.hash); 53 | console.log("transaction end"); 54 | const tokenId = parseToTokenId(receipt); 55 | return tokenId; 56 | } 57 | 58 | override async mintTokenForIPFS(mintForIPFSParam: MintForIPFSTxParam): Promise { 59 | const fee = await this.getFee(); 60 | let txSettings: unknown; 61 | if (mintForIPFSParam.gasSettings?.gasPrice) { 62 | txSettings = { 63 | value: fee, 64 | gasPrice: mintForIPFSParam.gasSettings?.gasPrice, 65 | }; 66 | } else { 67 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 68 | getFeeSettingsBasedOnEnvironment( 69 | mintForIPFSParam.gasSettings?.maxPriorityFeePerGas, 70 | mintForIPFSParam.gasSettings?.maxFeePerGas 71 | ); 72 | txSettings = { 73 | value: fee, 74 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 75 | maxFeePerGas: _maxFeePerGas, 76 | }; 77 | } 78 | console.log("transaction start"); 79 | const tx = await this.erc6150Contract.mint( 80 | mintForIPFSParam.metadataUrl, 81 | mintForIPFSParam.decryptUrl, 82 | mintForIPFSParam.parentId, 83 | mintForIPFSParam.feeNumerator, 84 | mintForIPFSParam.documentId, 85 | txSettings 86 | ); 87 | const receipt = await this.ethersProvider.waitForTransaction(tx.hash); 88 | console.log("transaction end"); 89 | const tokenId = parseToTokenId(receipt); 90 | return tokenId; 91 | } 92 | 93 | override async grantViewPermission(grantParam: GrantViewPermissionTxParam): Promise { 94 | let txSettings: unknown; 95 | if (grantParam.gasSettings?.gasPrice) { 96 | txSettings = { 97 | gasPrice: grantParam.gasSettings?.gasPrice, 98 | }; 99 | } else { 100 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 101 | getFeeSettingsBasedOnEnvironment( 102 | grantParam.gasSettings?.maxPriorityFeePerGas, 103 | grantParam.gasSettings?.maxFeePerGas 104 | ); 105 | txSettings = { 106 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 107 | maxFeePerGas: _maxFeePerGas, 108 | }; 109 | } 110 | const tx = await this.erc6150Contract.grantViewPermission( 111 | grantParam.tokenId, 112 | grantParam.grantee, 113 | grantParam.toDir, 114 | txSettings 115 | ); 116 | await this.ethersProvider.waitForTransaction(tx.hash); 117 | } 118 | 119 | async revokeDirPermission(tokenId: number, revoker: string, gasSettings?: GasSettings): Promise { 120 | let txSettings: unknown; 121 | if (gasSettings?.gasPrice) { 122 | txSettings = { 123 | gasPrice: gasSettings?.gasPrice, 124 | }; 125 | } else { 126 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 127 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 128 | txSettings = { 129 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 130 | maxFeePerGas: _maxFeePerGas, 131 | }; 132 | } 133 | const tx = await this.erc6150Contract.revokeDirPermission(tokenId, revoker, txSettings); 134 | await this.ethersProvider.waitForTransaction(tx.hash); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish npm package 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - '*' 9 | 10 | env: 11 | # Check available versions below 12 | # https://github.com/actions/node-versions/blob/main/versions-manifest.json 13 | node-version: "20.x" 14 | 15 | jobs: 16 | setup: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ env.node-version }} 23 | cache: 'npm' 24 | - name: node_modules cache 25 | uses: actions/cache@v3 26 | id: cache_node_modules 27 | with: 28 | # キャッシュキー完全一致しない場合 npm installする必要があるのでrestore-keysは使わない 29 | key: ${{ runner.os }}-build-node_modules-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/package.json') }} 30 | path: '**/node_modules' 31 | 32 | - name: Install Dependencies 33 | if: steps.cache_node_modules.outputs.cache-hit != 'true' 34 | run: npm install 35 | 36 | publish-core-evm-xrpl: 37 | if: contains(github.ref, 'tags/core') 38 | needs: setup 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: actions/setup-node@v4 43 | with: 44 | node-version: ${{ env.node-version }} 45 | cache: 'npm' 46 | - name: node_modules cache 47 | uses: actions/cache@v3 48 | with: 49 | key: ${{ runner.os }}-build-node_modules-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/package.json') }} 50 | path: '**/node_modules' 51 | - name: Build 52 | run: npm run build:all 53 | - name: Check if vwbl-core package version can be published 54 | uses: technote-space/package-version-check-action@v1 55 | with: 56 | PACKAGE_DIR: 'packages/core/' 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | PACKAGE_MANAGER: npm 59 | - name: Check if vwbl-sdk(evm chain) package version can be published 60 | uses: technote-space/package-version-check-action@v1 61 | with: 62 | PACKAGE_DIR: 'packages/evm/' 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | PACKAGE_MANAGER: npm 65 | - name: Check if vwbl-sdk-xrpl package version can be published 66 | uses: technote-space/package-version-check-action@v1 67 | with: 68 | PACKAGE_DIR: 'packages/xrpl/' 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | PACKAGE_MANAGER: npm 71 | - name: Publish vwbl-core package 72 | run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_ACCESS_TOKEN }}" > ~/.npmrc && npm run publish:core 73 | env: 74 | NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} 75 | - name: Publish vwbl-sdk(evm chain) package 76 | run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_ACCESS_TOKEN }}" > ~/.npmrc && npm run publish:evm 77 | env: 78 | NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} 79 | - name: Publish vwbl-sdk-xrpl package 80 | run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_ACCESS_TOKEN }}" > ~/.npmrc && npm run publish:xrpl 81 | env: 82 | NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} 83 | 84 | publish-evm: 85 | if: contains(github.ref, 'tags/evm') 86 | needs: setup 87 | runs-on: ubuntu-latest 88 | steps: 89 | - uses: actions/checkout@v4 90 | - uses: actions/setup-node@v4 91 | id: setup-node 92 | with: 93 | node-version: ${{ env.node-version }} 94 | cache: 'npm' 95 | - name: node_modules cache 96 | uses: actions/cache@v3 97 | with: 98 | key: ${{ runner.os }}-build-node_modules-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/package.json') }} 99 | path: '**/node_modules' 100 | - name: Build 101 | run: npm run build:all 102 | - name: Check if vwbl-sdk(evm chain) package version can be published 103 | uses: technote-space/package-version-check-action@v1 104 | with: 105 | PACKAGE_DIR: 'packages/evm/' 106 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 107 | PACKAGE_MANAGER: npm 108 | - name: Publish vwbl-sdk(evm chain) package 109 | run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_ACCESS_TOKEN }}" > ~/.npmrc && npm run publish:evm 110 | env: 111 | NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} 112 | 113 | publish-xrpl: 114 | if: contains(github.ref, 'tags/xrpl') 115 | needs: setup 116 | runs-on: ubuntu-latest 117 | steps: 118 | - uses: actions/checkout@v4 119 | - uses: actions/setup-node@v4 120 | with: 121 | node-version: ${{ env.node-version }} 122 | cache: 'npm' 123 | - name: node_modules cache 124 | uses: actions/cache@v3 125 | with: 126 | key: ${{ runner.os }}-build-node_modules-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/package.json') }} 127 | path: '**/node_modules' 128 | - name: Build 129 | run: npm run build:all 130 | - name: Check if vwbl-sdk-xrpl package version can be published 131 | uses: technote-space/package-version-check-action@v1 132 | with: 133 | PACKAGE_DIR: 'packages/xrpl/' 134 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 135 | PACKAGE_MANAGER: npm 136 | - name: Publish vwbl-sdk-xrpl package 137 | run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_ACCESS_TOKEN }}" > ~/.npmrc && npm run publish:xrpl 138 | env: 139 | NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} 140 | -------------------------------------------------------------------------------- /packages/core/src/storage/aws/upload.ts: -------------------------------------------------------------------------------- 1 | import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; 2 | import { fromCognitoIdentityPool, fromIni } from "@aws-sdk/credential-providers"; 3 | import { Upload } from "@aws-sdk/lib-storage"; 4 | import * as fs from "fs"; 5 | import * as Stream from "stream"; 6 | 7 | import { getMimeType } from "../../util"; 8 | import { PlainMetadata } from "../../vwbl/metadata"; 9 | import { EncryptLogic, FileOrPath } from "../../vwbl/types"; 10 | import { AWSConfig } from "./types"; 11 | 12 | export const uploadEncryptedFile = async ( 13 | fileName: string, 14 | encryptedContent: string | Uint8Array | Stream.Readable, 15 | uuid: string, 16 | awsConfig?: AWSConfig 17 | ): Promise => { 18 | if (!awsConfig || !awsConfig.bucketName.content) { 19 | throw new Error("bucket is not specified."); 20 | } 21 | if (!awsConfig.idPoolId && !awsConfig.profile) { 22 | throw new Error("aws credential environment variable is not specified."); 23 | } 24 | 25 | const credentials = awsConfig.idPoolId 26 | ? fromCognitoIdentityPool({ 27 | clientConfig: { region: awsConfig.region }, 28 | identityPoolId: awsConfig.idPoolId, 29 | }) 30 | : fromIni({ profile: awsConfig.profile }); 31 | const s3Client = new S3Client({ credentials }); 32 | 33 | const key = `data/${uuid}-${fileName}.vwbl`; 34 | const upload = new Upload({ 35 | client: s3Client, 36 | params: { 37 | Bucket: awsConfig.bucketName.content, 38 | Key: key, 39 | Body: encryptedContent, 40 | ACL: "public-read", 41 | }, 42 | }); 43 | 44 | await upload.done(); 45 | return `${awsConfig.cloudFrontUrl.replace(/\/$/, "")}/${key}`; 46 | }; 47 | 48 | export const uploadThumbnail = async ( 49 | thumbnailImage: FileOrPath, 50 | uuid: string, 51 | awsConfig?: AWSConfig 52 | ): Promise => { 53 | if (!awsConfig || !awsConfig.bucketName.content) { 54 | throw new Error("bucket is not specified."); 55 | } 56 | if (!awsConfig.idPoolId && !awsConfig.profile) { 57 | throw new Error("aws credential environment variable is not specified."); 58 | } 59 | 60 | const credentials = awsConfig.idPoolId 61 | ? fromCognitoIdentityPool({ 62 | clientConfig: { region: awsConfig.region }, 63 | identityPoolId: awsConfig.idPoolId, 64 | }) 65 | : fromIni({ profile: awsConfig.profile }); 66 | const s3Client = new S3Client({ credentials }); 67 | const fileName = thumbnailImage instanceof File ? thumbnailImage.name : thumbnailImage.split("/").slice(-1)[0]; //ファイル名の取得だけのためにpathを使いたくなかった 68 | 69 | const key = `data/${uuid}-${fileName}`; 70 | const type = getMimeType(thumbnailImage); 71 | const uploadCommand = new PutObjectCommand({ 72 | Bucket: awsConfig.bucketName.content, 73 | Key: key, 74 | Body: thumbnailImage instanceof File ? thumbnailImage : await fs.promises.readFile(thumbnailImage), 75 | ContentType: type, 76 | ACL: "public-read", 77 | }); 78 | 79 | await s3Client.send(uploadCommand); 80 | return `${awsConfig.cloudFrontUrl.replace(/\/$/, "")}/${key}`; 81 | }; 82 | 83 | export const uploadMetadata = async ( 84 | tokenId: string | number, 85 | name: string, 86 | description: string, 87 | previewImageUrl: string, 88 | encryptedDataUrls: string[], 89 | mimeType: string, 90 | encryptLogic: EncryptLogic, 91 | awsConfig?: AWSConfig 92 | ): Promise => { 93 | if (!awsConfig || !awsConfig.bucketName.content) { 94 | throw new Error("bucket is not specified."); 95 | } 96 | if (!awsConfig.idPoolId && !awsConfig.profile) { 97 | throw new Error("aws credential environment variable is not specified."); 98 | } 99 | 100 | const credentials = awsConfig.idPoolId 101 | ? fromCognitoIdentityPool({ 102 | clientConfig: { region: awsConfig.region }, 103 | identityPoolId: awsConfig.idPoolId, 104 | }) 105 | : fromIni({ profile: awsConfig.profile }); 106 | const s3Client = new S3Client({ credentials }); 107 | 108 | const metadata: PlainMetadata = { 109 | name, 110 | description, 111 | image: previewImageUrl, 112 | encrypted_data: encryptedDataUrls, 113 | mime_type: mimeType, 114 | encrypt_logic: encryptLogic, 115 | }; 116 | const key = `metadata/${tokenId}`; 117 | const uploadCommand = new PutObjectCommand({ 118 | Bucket: awsConfig.bucketName.metadata, 119 | Key: key, 120 | Body: JSON.stringify(metadata), 121 | ContentType: "application/json", 122 | ACL: "public-read", 123 | }); 124 | await s3Client.send(uploadCommand); 125 | 126 | return `${awsConfig.cloudFrontUrl.replace(/\/$/, "")}/${key}`; 127 | }; 128 | 129 | export const uploadDirectoryToS3 = (directoryPath: string, awsConfig?: AWSConfig) => { 130 | if (!awsConfig || !awsConfig.bucketName.content) { 131 | throw new Error("bucket is not specified."); 132 | } 133 | if (!awsConfig.idPoolId && !awsConfig.profile) { 134 | throw new Error("aws credential environment variable is not specified."); 135 | } 136 | 137 | const credentials = awsConfig.idPoolId 138 | ? fromCognitoIdentityPool({ 139 | clientConfig: { region: awsConfig.region }, 140 | identityPoolId: awsConfig.idPoolId, 141 | }) 142 | : fromIni({ profile: awsConfig.profile }); 143 | const s3Client = new S3Client({ credentials }); 144 | 145 | fs.readdir(directoryPath, (err, files) => { 146 | if (err) throw err; 147 | files.forEach((file) => { 148 | const filePath = `${directoryPath}/${file}`; 149 | fs.readFile(filePath, async (err, data) => { 150 | if (err) throw err; 151 | 152 | const upload = new Upload({ 153 | client: s3Client, 154 | params: { 155 | Bucket: awsConfig.bucketName.content, 156 | Key: `${directoryPath}/${file}`, 157 | Body: data, 158 | ACL: "public-read", 159 | }, 160 | }); 161 | await upload.done(); 162 | }); 163 | }); 164 | }); 165 | }; 166 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/types/VWBLERC721ERC6150Type.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EncryptLogic, 3 | FileOrPath, 4 | ProgressSubscriber, 5 | UploadEncryptedFile, 6 | UploadEncryptedFileToIPFS, 7 | UploadMetadata, 8 | UploadMetadataToIPFS, 9 | UploadThumbnail, 10 | UploadThumbnailToIPFS, 11 | } from "vwbl-core"; 12 | import { GasSettings } from "./index"; 13 | 14 | export type ManagedCreateToken = { 15 | // Interface for ERC721 16 | ( 17 | name: string, 18 | description: string, 19 | plainFile: FileOrPath | FileOrPath[], 20 | thumbnailImage: FileOrPath, 21 | feeNumerator: number, 22 | encryptLogic: EncryptLogic, 23 | uploadEncryptedFileCallback?: UploadEncryptedFile, 24 | uploadThumbnailCallback?: UploadThumbnail, 25 | uploadMetadataCallBack?: UploadMetadata, 26 | subscriber?: ProgressSubscriber, 27 | gasSettings?: GasSettings 28 | ): Promise; 29 | 30 | // Interface for ERC6150 31 | ( 32 | name: string, 33 | description: string, 34 | plainFile: FileOrPath | FileOrPath[], 35 | thumbnailImage: FileOrPath, 36 | feeNumerator: number, 37 | encryptLogic: EncryptLogic, 38 | uploadEncryptedFileCallback?: UploadEncryptedFile, 39 | uploadThumbnailCallback?: UploadThumbnail, 40 | uploadMetadataCallBack?: UploadMetadata, 41 | subscriber?: ProgressSubscriber, 42 | gasSettings?: GasSettings, 43 | parentId?: number 44 | ): Promise; 45 | }; 46 | 47 | export type ManagedCreateTokenForIPFS = { 48 | // Interface for ERC721 49 | ( 50 | name: string, 51 | description: string, 52 | plainFile: FileOrPath | FileOrPath[], 53 | thumbnailImage: FileOrPath, 54 | feeNumerator: number, 55 | encryptLogic: EncryptLogic, 56 | uploadEncryptedFileCallback?: UploadEncryptedFileToIPFS, 57 | uploadThumbnailCallback?: UploadThumbnailToIPFS, 58 | uploadMetadataCallBack?: UploadMetadataToIPFS, 59 | subscriber?: ProgressSubscriber, 60 | gasSettings?: GasSettings 61 | ): Promise; 62 | 63 | // Interface for ERC6150 64 | ( 65 | name: string, 66 | description: string, 67 | plainFile: FileOrPath | FileOrPath[], 68 | thumbnailImage: FileOrPath, 69 | feeNumerator: number, 70 | encryptLogic: EncryptLogic, 71 | uploadEncryptedFileCallback?: UploadEncryptedFileToIPFS, 72 | uploadThumbnailCallback?: UploadThumbnailToIPFS, 73 | uploadMetadataCallBack?: UploadMetadataToIPFS, 74 | subscriber?: ProgressSubscriber, 75 | gasSettings?: GasSettings, 76 | parentId?: number 77 | ): Promise; 78 | }; 79 | 80 | export type MintToken = { 81 | // Interface for ERC721 82 | (feeNumerator: number, gasSettings?: GasSettings): Promise; 83 | 84 | // Interface for ERC6150 85 | (feeNumerator: number, gasSettings?: GasSettings, parentId?: number): Promise; 86 | }; 87 | 88 | export type MintTokenForIPFS = { 89 | // Interface for ERC721 90 | (metadataUrl: string, feeNumerator: number, gasSettings?: GasSettings): Promise; 91 | 92 | // Interface for ERC6150 93 | (metadataUrl: string, feeNumerator: number, gasSettings?: GasSettings, parentId?: number): Promise; 94 | }; 95 | 96 | export type GrantViewPermission = { 97 | // Interface for ERC721 98 | (tokenId: number, grantee: string, gasSettings?: GasSettings): Promise; 99 | 100 | // Interface for ERC6150 101 | (tokenId: number, grantee: string, gasSettings?: GasSettings, toDir?: boolean): Promise; 102 | }; 103 | 104 | export type ManagedCreateTokenMetatx = { 105 | // Interface for ERC721 106 | ( 107 | name: string, 108 | description: string, 109 | plainFile: FileOrPath | FileOrPath[], 110 | thumbnailImage: FileOrPath, 111 | feeNumerator: number, 112 | encryptLogic: EncryptLogic, 113 | uploadEncryptedFileCallback?: UploadEncryptedFile, 114 | uploadThumbnailCallback?: UploadThumbnail, 115 | uploadMetadataCallBack?: UploadMetadata, 116 | subscriber?: ProgressSubscriber 117 | ): Promise; 118 | 119 | // Interface for ERC6150 120 | ( 121 | name: string, 122 | description: string, 123 | plainFile: FileOrPath | FileOrPath[], 124 | thumbnailImage: FileOrPath, 125 | feeNumerator: number, 126 | encryptLogic: EncryptLogic, 127 | uploadEncryptedFileCallback?: UploadEncryptedFile, 128 | uploadThumbnailCallback?: UploadThumbnail, 129 | uploadMetadataCallBack?: UploadMetadata, 130 | subscriber?: ProgressSubscriber, 131 | parentId?: number 132 | ): Promise; 133 | }; 134 | 135 | export type ManagedCreateTokenForIPFSMetaTx = { 136 | // Interface for ERC721 137 | ( 138 | name: string, 139 | description: string, 140 | plainFile: FileOrPath | FileOrPath[], 141 | thumbnailImage: FileOrPath, 142 | feeNumerator: number, 143 | encryptLogic: EncryptLogic, 144 | uploadEncryptedFileCallback?: UploadEncryptedFileToIPFS, 145 | uploadThumbnailCallback?: UploadThumbnailToIPFS, 146 | uploadMetadataCallBack?: UploadMetadataToIPFS, 147 | subscriber?: ProgressSubscriber 148 | ): Promise; 149 | 150 | // Interface for ERC6150 151 | ( 152 | name: string, 153 | description: string, 154 | plainFile: FileOrPath | FileOrPath[], 155 | thumbnailImage: FileOrPath, 156 | feeNumerator: number, 157 | encryptLogic: EncryptLogic, 158 | uploadEncryptedFileCallback?: UploadEncryptedFileToIPFS, 159 | uploadThumbnailCallback?: UploadThumbnailToIPFS, 160 | uploadMetadataCallBack?: UploadMetadataToIPFS, 161 | subscriber?: ProgressSubscriber, 162 | parentId?: number 163 | ): Promise; 164 | }; 165 | 166 | export type MintTokenMetaTx = { 167 | // Interface for ERC721 168 | (feeNumerator: number): Promise; 169 | 170 | // Interface for ERC6150 171 | (feeNumerator: number, parentId?: number): Promise; 172 | }; 173 | 174 | export type MintTokenForIPFSMetaTx = { 175 | // Interface for ERC721 176 | (metadataUrl: string, feeNumerator: number): Promise; 177 | 178 | // Interface for ERC6150 179 | (metadataUrl: string, feeNumerator: number, parentId?: number): Promise; 180 | }; 181 | 182 | export type GrantViewPermissionMetaTx = { 183 | // Interface for ERC721 184 | (tokenId: number, grantee: string): Promise; 185 | 186 | // Interface for ERC6150 187 | (tokenId: number, grantee: string, toDir?: boolean): Promise; 188 | }; 189 | -------------------------------------------------------------------------------- /packages/core/src/vwbl/core.ts: -------------------------------------------------------------------------------- 1 | import * as Stream from "stream"; 2 | 3 | import { AWSConfig, uploadMetadata } from "../storage"; 4 | import { IPFSConfig } from "../storage/ipfs"; 5 | import { uploadMetadataToIPFS } from "../storage/ipfs"; 6 | import { 7 | createRandomKey, 8 | decryptFile, 9 | decryptStream, 10 | encryptFile, 11 | encryptStream, 12 | encryptString, 13 | toBase64FromFile, 14 | } from "../util"; 15 | import { CoreConstructorProps, EncryptLogic, UploadContentType, UploadMetadata, UploadMetadataType } from "./types"; 16 | 17 | export class VWBLCore { 18 | private uploadMetadataType: UploadMetadataType; 19 | private awsConfig?: AWSConfig; 20 | private ipfsConfig?: IPFSConfig; 21 | 22 | // eslint-disable-next-line @typescript-eslint/no-empty-function 23 | constructor(props: CoreConstructorProps) { 24 | const { uploadContentType, uploadMetadataType, awsConfig, ipfsConfig } = props; 25 | this.uploadMetadataType = uploadMetadataType; 26 | if (uploadContentType === UploadContentType.S3 || uploadMetadataType === UploadMetadataType.S3) { 27 | if (!awsConfig) { 28 | throw new Error("please specify S3 bucket."); 29 | } 30 | this.awsConfig = awsConfig; 31 | } else if (uploadContentType === UploadContentType.IPFS || uploadMetadataType === UploadMetadataType.IPFS) { 32 | if (!ipfsConfig) { 33 | throw new Error("please specify pinata config of IPFS."); 34 | } 35 | this.ipfsConfig = ipfsConfig; 36 | } 37 | } 38 | 39 | /** 40 | * Uplod Metadata 41 | * 42 | * @remarks 43 | * By default, metadata will be uploaded to Amazon S3. 44 | * You need to pass `uploadMetadataCallBack` if you upload metadata to a storage other than Amazon S3. 45 | * 46 | * @param tokenId - The ID of NFT 47 | * @param name - The NFT name 48 | * @param description - The NFT description 49 | * @param thumbnailImageUrl - The URL of the thumbnail image 50 | * @param encryptedDataUrls - The URL of the encrypted file data 51 | * @param mimeType - The mime type of encrypted file data 52 | * @param encryptLogic - Select ether "base64" or "binary". Selection criteria: "base64" -> sutable for small data. "binary" -> sutable for large data. 53 | * @param uploadMetadataCallBack - Optional: the function for uploading metadata 54 | */ 55 | uploadMetadata = async ( 56 | tokenId: number, 57 | name: string, 58 | description: string, 59 | thumbnailImageUrl: string, 60 | encryptedDataUrls: string[], 61 | mimeType: string, 62 | encryptLogic: EncryptLogic, 63 | uploadMetadataCallBack?: UploadMetadata 64 | ): Promise => { 65 | const uploadMetadataFunction = 66 | this.uploadMetadataType === UploadMetadataType.S3 ? uploadMetadata : uploadMetadataCallBack; 67 | if (!uploadMetadataFunction) { 68 | throw new Error("please specify upload metadata type or give callback"); 69 | } 70 | await uploadMetadataFunction( 71 | tokenId, 72 | name, 73 | description, 74 | thumbnailImageUrl, 75 | encryptedDataUrls, 76 | mimeType, 77 | encryptLogic, 78 | this.awsConfig 79 | ); 80 | }; 81 | 82 | /** 83 | * Uplod Metadata to IPFS 84 | * 85 | * @remarks 86 | * Metadata will be uploaded to IPFS. 87 | * 88 | * @param tokenId - The ID of NFT 89 | * @param name - The NFT name 90 | * @param description - The NFT description 91 | * @param thumbnailImageUrl - The URL of the thumbnail image 92 | * @param encryptedDataUrls - The URL of the encrypted file data 93 | * @param mimeType - The mime type of encrypted file data 94 | * @param encryptLogic - Select ether "base64" or "binary". Selection criteria: "base64" -> sutable for small data. "binary" -> sutable for large data. 95 | */ 96 | uploadMetadataToIPFS = async ( 97 | name: string, 98 | description: string, 99 | thumbnailImageUrl: string, 100 | encryptedDataUrls: string[], 101 | mimeType: string, 102 | encryptLogic: EncryptLogic 103 | ): Promise => { 104 | const metadataUrl = await uploadMetadataToIPFS( 105 | name, 106 | description, 107 | thumbnailImageUrl, 108 | encryptedDataUrls, 109 | mimeType, 110 | encryptLogic, 111 | this.ipfsConfig 112 | ); 113 | return metadataUrl; 114 | }; 115 | 116 | /** 117 | * Create a key used for encryption and decryption 118 | * 119 | * @returns Random string generated by uuid 120 | */ 121 | createKey = (): string => { 122 | return createRandomKey(); 123 | }; 124 | 125 | /** 126 | * Encode `plainData` to Base64 and encrypt it 127 | * 128 | * @param plainData - The data that only NFT owner can view 129 | * @param key - The key generated by {@link VWBL.createKey} 130 | * @returns Encrypted file data 131 | */ 132 | encryptDataViaBase64 = async (plainData: File, key: string): Promise => { 133 | const content = await toBase64FromFile(plainData); 134 | return encryptString(content, key); 135 | }; 136 | 137 | /** 138 | * Encrypt `plainData` 139 | * 140 | * @param plainFile - The data that only NFT owner can view 141 | * @param key - The key generated by {@link VWBL.createKey} 142 | * @returns Encrypted file data 143 | */ 144 | encryptFile = async (plainFile: File, key: string): Promise => { 145 | return encryptFile(plainFile, key); 146 | }; 147 | 148 | /** 149 | * Decrypt `encryptFile` 150 | * 151 | * @param encryptFile - The data that only NFT owner can view 152 | * @param key - The key generated by {@link VWBL.createKey} 153 | * @returns Encrypted file data 154 | */ 155 | decryptFile = async (encryptFile: Uint8Array, key: string): Promise => { 156 | return decryptFile(encryptFile, key); 157 | }; 158 | 159 | /** 160 | * Encrypt `plainData` 161 | * 162 | * @param plainFile - The data that only NFT owner can view 163 | * @param key - The key generated by {@link VWBL.createKey} 164 | * @returns Encrypted file data 165 | */ 166 | encryptStream = (plainFile: Stream, key: string): Stream => { 167 | return encryptStream(plainFile, key); 168 | }; 169 | 170 | /** 171 | * Decrypt `encryptFile` 172 | * 173 | * @param encryptFile - The data that only NFT owner can view 174 | * @param key - The key generated by {@link VWBL.createKey} 175 | * @returns Encrypted file data 176 | */ 177 | decryptStream = (encryptFile: Stream, key: string): Stream => { 178 | return decryptStream(encryptFile, key); 179 | }; 180 | } -------------------------------------------------------------------------------- /packages/evm/src/vwbl/blockchain/erc6150/VWBLProtocol.ts: -------------------------------------------------------------------------------- 1 | import { Web3 } from "web3"; 2 | 3 | import vwblERC6150 from "../../../contract/VWBLERC6150ERC2981.json"; 4 | import vwblERC6150IPFS from "../../../contract/VWBLERC6150ERC2981ForMetadata.json"; 5 | import { getFeeSettingsBasedOnEnvironment } from "../../../util/transactionHelper"; 6 | import { GasSettings, GrantViewPermissionTxParam, MintForIPFSTxParam, MintTxParam } from "../../types"; 7 | import { VWBLNFT } from "../erc721/VWBLProtocol"; 8 | 9 | export class VWBLERC6150Web3 extends VWBLNFT { 10 | private erc6150Contract: any; // eslint-disable-line @typescript-eslint/no-explicit-any 11 | 12 | constructor(web3: Web3, address: string, isIpfs: boolean) { 13 | super(web3, address, isIpfs); 14 | this.erc6150Contract = isIpfs 15 | ? new web3.eth.Contract(vwblERC6150IPFS.abi, address) 16 | : new web3.eth.Contract(vwblERC6150.abi, address); 17 | } 18 | 19 | override async mintToken(mintParam: MintTxParam): Promise { 20 | const myAddress = (await this.web3.eth.getAccounts())[0]; 21 | const fee = await this.getFee(); 22 | let txSettings: unknown; 23 | if (mintParam.gasSettings?.gasPrice) { 24 | const gas = await this.erc6150Contract.methods 25 | .mint(mintParam.decryptUrl, mintParam.parentId, mintParam.feeNumerator, mintParam.documentId) 26 | .estimateGas({ from: myAddress, value: fee }); 27 | txSettings = { 28 | from: myAddress, 29 | value: fee, 30 | gasPrice: mintParam.gasSettings?.gasPrice, 31 | gas, 32 | }; 33 | } else { 34 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 35 | getFeeSettingsBasedOnEnvironment( 36 | mintParam.gasSettings?.maxPriorityFeePerGas, 37 | mintParam.gasSettings?.maxFeePerGas 38 | ); 39 | txSettings = { 40 | from: myAddress, 41 | value: fee, 42 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 43 | maxFeePerGas: _maxFeePerGas, 44 | }; 45 | } 46 | console.log("transaction start"); 47 | const receipt = await this.erc6150Contract.methods 48 | .mint(mintParam.decryptUrl, mintParam.parentId, mintParam.feeNumerator, mintParam.documentId) 49 | .send(txSettings); 50 | console.log("transaction end"); 51 | const tokenId: number = receipt.events.Transfer.returnValues.tokenId; 52 | return tokenId; 53 | } 54 | 55 | override async mintTokenForIPFS(mintForIPFSParam: MintForIPFSTxParam): Promise { 56 | const myAddress = (await this.web3.eth.getAccounts())[0]; 57 | const fee = await this.getFee(); 58 | let txSettings: unknown; 59 | if (mintForIPFSParam.gasSettings?.gasPrice) { 60 | const gas = await this.erc6150Contract.methods 61 | .mint( 62 | mintForIPFSParam.metadataUrl, 63 | mintForIPFSParam.decryptUrl, 64 | mintForIPFSParam.parentId, 65 | mintForIPFSParam.feeNumerator, 66 | mintForIPFSParam.documentId 67 | ) 68 | .estimateGas({ from: myAddress, value: fee }); 69 | txSettings = { 70 | from: myAddress, 71 | value: fee, 72 | gasPrice: mintForIPFSParam.gasSettings?.gasPrice, 73 | gas, 74 | }; 75 | } else { 76 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 77 | getFeeSettingsBasedOnEnvironment( 78 | mintForIPFSParam.gasSettings?.maxPriorityFeePerGas, 79 | mintForIPFSParam.gasSettings?.maxFeePerGas 80 | ); 81 | txSettings = { 82 | from: myAddress, 83 | value: fee, 84 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 85 | maxFeePerGas: _maxFeePerGas, 86 | }; 87 | } 88 | console.log("transaction start"); 89 | const receipt = await this.erc6150Contract.methods 90 | .mint( 91 | mintForIPFSParam.metadataUrl, 92 | mintForIPFSParam.decryptUrl, 93 | mintForIPFSParam.parentId, 94 | mintForIPFSParam.feeNumerator, 95 | mintForIPFSParam.documentId 96 | ) 97 | .send(txSettings); 98 | console.log("transaction end"); 99 | const tokenId: number = receipt.events.Transfer.returnValues.tokenId; 100 | return tokenId; 101 | } 102 | 103 | override async grantViewPermission(grantParam: GrantViewPermissionTxParam): Promise { 104 | const myAddress = (await this.web3.eth.getAccounts())[0]; 105 | let txSettings: unknown; 106 | if (grantParam.gasSettings?.gasPrice) { 107 | const gas = await this.erc6150Contract.methods 108 | .grantViewPermission(grantParam.tokenId, grantParam.grantee, grantParam.toDir) 109 | .estimateGas({ from: myAddress }); 110 | txSettings = { 111 | from: myAddress, 112 | gasPrice: grantParam.gasSettings?.gasPrice, 113 | gas, 114 | }; 115 | } else { 116 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 117 | getFeeSettingsBasedOnEnvironment( 118 | grantParam.gasSettings?.maxPriorityFeePerGas, 119 | grantParam.gasSettings?.maxFeePerGas 120 | ); 121 | txSettings = { 122 | from: myAddress, 123 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 124 | maxFeePerGas: _maxFeePerGas, 125 | }; 126 | } 127 | await this.erc6150Contract.methods 128 | .grantViewPermission(grantParam.tokenId, grantParam.grantee, grantParam.toDir) 129 | .send(txSettings); 130 | } 131 | 132 | async revokeDirPermission(tokenId: number, revoker: string, gasSettings?: GasSettings): Promise { 133 | const myAddress = (await this.web3.eth.getAccounts())[0]; 134 | let txSettings: unknown; 135 | if (gasSettings?.gasPrice) { 136 | const gas = await this.erc6150Contract.methods 137 | .revokeDirPermission(tokenId, revoker) 138 | .estimateGas({ from: myAddress }); 139 | txSettings = { 140 | from: myAddress, 141 | gasPrice: gasSettings?.gasPrice, 142 | gas, 143 | }; 144 | } else { 145 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 146 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 147 | txSettings = { 148 | from: myAddress, 149 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 150 | maxFeePerGas: _maxFeePerGas, 151 | }; 152 | } 153 | await this.erc6150Contract.methods.revokeDirPermission(tokenId, revoker).send(txSettings); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /packages/xrpl/examples/vwbl-xrpl.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { Wallet } from "xrpl"; 3 | 4 | import { VWBLXRPL } from "vwbl-sdk-xrpl"; 5 | import { UploadContentType, UploadMetadataType } from "vwbl-core"; 6 | import { XummSdk } from "xumm-sdk"; 7 | import { 8 | XummJsonTransaction, 9 | XummPostPayloadBodyJson, 10 | } from "xumm-sdk/dist/src/types"; 11 | 12 | const USER_ADDRESS = "..."; 13 | const wallet = Wallet.fromSecret("..."); 14 | const xumm = new XummSdk("{XUMM_API_KEY}", "{XUMM_API_SECRET}"); 15 | 16 | async function main() { 17 | const vwblXrpl = new VWBLXRPL({ 18 | xrplChainId: 0, // Mainnet:0 / Testnet:1 / Devnet:2 19 | vwblNetworkUrl: "https://vwbl.network", 20 | uploadContentType: UploadContentType.IPFS, 21 | uploadMetadataType: UploadMetadataType.IPFS, 22 | ipfsConfig: { 23 | apiKey: "{PINATA_APIKEY", 24 | apiSecret: "{PINATA_APISECRET}", 25 | }, 26 | }); 27 | 28 | const fileBuffer = fs.readFileSync("{FILE_PATH}"); 29 | const file = new File([fileBuffer], "sample.png", { type: "image/png" }); 30 | 31 | /* 32 | Mint & Set key 33 | */ 34 | 35 | const { mintTxJson, metadataUrl } = await vwblXrpl.generateMintTokenTxForIPFS( 36 | USER_ADDRESS, 37 | 0, 38 | true, 39 | false, 40 | "test", 41 | "vwbl test", 42 | file, 43 | "{FILE_PATH}" 44 | ); 45 | // *make sure you don't submit tx 46 | const mintTxPayload: XummPostPayloadBodyJson = { 47 | txjson: mintTxJson as unknown as XummJsonTransaction, 48 | options: { 49 | submit: false, 50 | }, 51 | }; 52 | 53 | let signedMintTxHex = ""; 54 | try { 55 | const response = await xumm.payload.create(mintTxPayload, true); 56 | 57 | if (response) { 58 | console.log(response.next.always); 59 | const startTime = Date.now(); 60 | 61 | let completed = false; 62 | while (!completed) { 63 | try { 64 | const status = await xumm.payload.get(response.uuid); 65 | if (status?.meta?.resolved) { 66 | completed = true; 67 | if (status.meta.signed && status.response.hex) { 68 | console.log("Transaction signed by user:"); 69 | signedMintTxHex = status.response.hex; 70 | break; 71 | } else { 72 | console.log("Transaction not signed by user:", status); 73 | } 74 | } else { 75 | await new Promise((resolve) => setTimeout(resolve, 3000)); 76 | 77 | if (Date.now() - startTime > 180000) { 78 | console.log("Time our reached"); 79 | completed = true; 80 | } 81 | } 82 | } catch (error) { 83 | console.error("Error checking payload status:", error); 84 | break; 85 | } 86 | } 87 | } 88 | } catch (e) { 89 | console.error(e); 90 | } 91 | 92 | const { tokenId, emptyTxObject } = await vwblXrpl.mintAndGenerateTxForIPFS( 93 | signedMintTxHex, 94 | metadataUrl, 95 | USER_ADDRESS 96 | ); 97 | 98 | const txObj: XummPostPayloadBodyJson = { 99 | txjson: emptyTxObject as unknown as XummJsonTransaction, 100 | // * make sure you don't submit tx on client side 101 | options: { 102 | submit: false, 103 | }, 104 | }; 105 | 106 | const resp = await xumm.payload.create(txObj, true); 107 | let signedEmptyTxHex = ""; 108 | if (resp) { 109 | console.log(resp.next.always); 110 | const startTime = Date.now(); 111 | 112 | let completed = false; 113 | while (!completed) { 114 | try { 115 | const status = await xumm.payload.get(resp.uuid); 116 | if (status?.meta?.resolved) { 117 | completed = true; 118 | if (status.meta.signed && status.response.hex) { 119 | console.log("Transaction signed by user:"); 120 | signedEmptyTxHex = status.response.hex; 121 | break; 122 | } else { 123 | console.log("Transaction not signed by user:", status); 124 | } 125 | } else { 126 | await new Promise((resolve) => setTimeout(resolve, 3000)); 127 | 128 | if (Date.now() - startTime > 180000) { 129 | console.log("Time our reached"); 130 | completed = true; 131 | } 132 | } 133 | } catch (error) { 134 | console.error("Error checking payload status:", error); 135 | break; 136 | } 137 | } 138 | } 139 | 140 | const tId = await vwblXrpl.createManagedTokenForIPFS( 141 | tokenId, 142 | signedEmptyTxHex, 143 | wallet.publicKey 144 | ); 145 | console.log(tId); 146 | 147 | /* 148 | Get key & Extract Metadata 149 | */ 150 | 151 | const txForSigning = await vwblXrpl.generateTxForSigning(USER_ADDRESS); 152 | // *make sure you don't submit tx 153 | const signingTxPayload: XummPostPayloadBodyJson = { 154 | txjson: txForSigning as unknown as XummJsonTransaction, 155 | options: { 156 | submit: false, 157 | }, 158 | }; 159 | 160 | let signedTx = ""; 161 | try { 162 | const response = await xumm.payload.create(signingTxPayload, true); 163 | 164 | if (response) { 165 | console.log(response.next.always); 166 | const startTime = Date.now(); 167 | let completed = false; 168 | while (!completed) { 169 | try { 170 | const status = await xumm.payload.get(response.uuid); 171 | if (status?.meta?.resolved) { 172 | completed = true; 173 | if (status.meta.signed && status.response.hex) { 174 | console.log("Transaction signed by user:"); 175 | signedTx = status.response.hex; 176 | break; 177 | } else { 178 | console.log("Transaction not signed by user:", status); 179 | } 180 | } else { 181 | await new Promise((resolve) => setTimeout(resolve, 3000)); 182 | 183 | if (Date.now() - startTime > 180000) { 184 | console.log("Time our reached"); 185 | completed = true; 186 | } 187 | } 188 | } catch (error) { 189 | console.error("Error checking payload status:", error); 190 | break; 191 | } 192 | } 193 | } 194 | } catch (e) { 195 | console.error(e); 196 | } 197 | 198 | const metadata = await vwblXrpl.extractMetadata( 199 | tId, 200 | signedTx, 201 | wallet.publicKey 202 | ); 203 | if (metadata) { 204 | console.log(metadata.id); 205 | console.log(metadata.fileName); 206 | console.log(metadata.image); 207 | } 208 | } 209 | 210 | main(); 211 | -------------------------------------------------------------------------------- /packages/evm/test/large/vwbl/mintOnPolygon.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import * as dotenv from "dotenv"; 3 | import { Web3 } from "web3"; 4 | import { ethers } from "ethers"; 5 | import { 6 | ManageKeyType, 7 | UploadContentType, 8 | UploadMetadataType, 9 | } from "vwbl-core"; 10 | import { VWBL } from "../../../src/vwbl"; 11 | import HDWalletProvider from "@truffle/hdwallet-provider"; 12 | import * as FileAPI from "file-api"; 13 | const File = FileAPI.File; 14 | dotenv.config(); 15 | 16 | type GasInfo = { 17 | safeLow: { maxPriorityFee: number, maxFee: number }, 18 | standard: { maxPriorityFee: number, maxFee: number }, 19 | fast: { maxPriorityFee: number, maxFee: number }, 20 | estimatedBaseFee: number, 21 | blockTime: number, 22 | blockNumber: number 23 | } 24 | 25 | const providerUrl = process.env.POLYGON_PROVIDER_URL; 26 | const nftContractAddr = "0xdE8Ac10E93698F6805E2B69599854408d1386417"; //polygon 27 | const networkUrl = "https://vwbl.network"; 28 | // preparation for web3.js 29 | const hdWalletProvider = new HDWalletProvider({ 30 | privateKeys: [process.env.PRIVATE_KEY as string], 31 | providerOrUrl: providerUrl 32 | }) 33 | const web3 = new Web3(hdWalletProvider as any); 34 | // preparation for ethers.js 35 | const privateKey = process.env.PRIVATE_KEY as string; 36 | const ethProvider = new ethers.providers.JsonRpcProvider(providerUrl); 37 | const ethSigner = new ethers.Wallet(privateKey, ethProvider); 38 | 39 | describe("VWBL with web3.js", () => { 40 | const vwbl = new VWBL({ 41 | ipfsConfig: undefined, 42 | awsConfig: undefined, 43 | contractAddress: nftContractAddr, 44 | manageKeyType: ManageKeyType.VWBL_NETWORK_SERVER, 45 | uploadContentType: UploadContentType.IPFS, 46 | uploadMetadataType: UploadMetadataType.IPFS, 47 | vwblNetworkUrl: networkUrl, 48 | web3, 49 | }); 50 | 51 | const testSubscriber = { 52 | kickStep: () => {}, 53 | }; 54 | 55 | it("mint token with gas settings", async () => { 56 | await vwbl.sign(); 57 | 58 | const gasInfo = await fetchGasInfo(); 59 | if(!gasInfo){ 60 | throw Error('failed to fetch gas information about polygon') 61 | } 62 | console.log(gasInfo.standard); 63 | const maxPriorityFee_wei = Number(web3.utils.toWei(String(gasInfo.standard.maxPriorityFee.toFixed(9)), 'gwei')); 64 | const maxFee_wei = Number(web3.utils.toWei(String(gasInfo.standard.maxFee.toFixed(9)), 'gwei')); 65 | 66 | const tokenId = await vwbl.managedCreateTokenForIPFS( 67 | "test token", 68 | "test", 69 | new File({ 70 | name: "thumbnail image", 71 | type: "image/png", 72 | buffer: Buffer.alloc(100), 73 | }), 74 | new File({ 75 | name: "plain data", 76 | type: "image/png", 77 | buffer: Buffer.alloc(100), 78 | }), 79 | 10, 80 | "base64", 81 | undefined, 82 | undefined, 83 | undefined, 84 | testSubscriber, 85 | { maxPriorityFeePerGas: maxPriorityFee_wei, maxFeePerGas: maxFee_wei } 86 | ); 87 | console.log(tokenId, typeof tokenId); 88 | expect(typeof tokenId).equal("string"); //WARNING:The return value type for 'tokenId' is a string. 89 | }); 90 | 91 | it.skip("mint token without gas settings", async () => { 92 | await vwbl.sign(); 93 | 94 | const tokenId = await vwbl.managedCreateTokenForIPFS( 95 | "test token", 96 | "test", 97 | new File({ 98 | name: "thumbnail image", 99 | type: "image/png", 100 | buffer: Buffer.alloc(100), 101 | }), 102 | new File({ 103 | name: "plain data", 104 | type: "image/png", 105 | buffer: Buffer.alloc(100), 106 | }), 107 | 10, 108 | "base64", 109 | ); 110 | console.log(tokenId, typeof tokenId); 111 | expect(typeof tokenId).equal("string"); //WARNING:The return value type for 'tokenId' is a string. 112 | }); 113 | }); 114 | 115 | describe("VWBL with ethers.js", () => { 116 | const vwbl = new VWBL({ 117 | ipfsConfig: undefined, 118 | awsConfig: undefined, 119 | contractAddress: nftContractAddr, 120 | manageKeyType: ManageKeyType.VWBL_NETWORK_SERVER, 121 | uploadContentType: UploadContentType.IPFS, 122 | uploadMetadataType: UploadMetadataType.IPFS, 123 | vwblNetworkUrl: networkUrl, 124 | ethersProvider: ethProvider, 125 | ethersSigner: ethSigner, 126 | }); 127 | 128 | const testSubscriber = { 129 | kickStep: () => {}, 130 | }; 131 | 132 | it("mint token with gas settings", async () => { 133 | await vwbl.sign(); 134 | 135 | const gasInfo = await fetchGasInfo(); 136 | if(!gasInfo){ 137 | throw Error('failed to fetch gas information about polygon') 138 | } 139 | console.log(gasInfo.standard); 140 | const maxPriorityFee_wei = Number(web3.utils.toWei(String(gasInfo.standard.maxPriorityFee.toFixed(9)), 'gwei')); 141 | const maxFee_wei = Number(web3.utils.toWei(String(gasInfo.standard.maxFee.toFixed(9)), 'gwei')); 142 | 143 | const tokenId = await vwbl.managedCreateTokenForIPFS( 144 | "test token", 145 | "test", 146 | new File({ 147 | name: "thumbnail image", 148 | type: "image/png", 149 | buffer: Buffer.alloc(100), 150 | }), 151 | new File({ 152 | name: "plain data", 153 | type: "image/png", 154 | buffer: Buffer.alloc(100), 155 | }), 156 | 10, 157 | "base64", 158 | undefined, 159 | undefined, 160 | undefined, 161 | testSubscriber, 162 | { maxPriorityFeePerGas: maxPriorityFee_wei, maxFeePerGas: maxFee_wei } 163 | ); 164 | console.log(tokenId, typeof tokenId); 165 | expect(typeof tokenId).equal("number"); 166 | }); 167 | 168 | it.skip("mint token without gas settings", async () => { 169 | await vwbl.sign(); 170 | 171 | const tokenId = await vwbl.managedCreateTokenForIPFS( 172 | "test token", 173 | "test", 174 | new File({ 175 | name: "thumbnail image", 176 | type: "image/png", 177 | buffer: Buffer.alloc(100), 178 | }), 179 | new File({ 180 | name: "plain data", 181 | type: "image/png", 182 | buffer: Buffer.alloc(100), 183 | }), 184 | 10, 185 | "base64", 186 | ); 187 | console.log(tokenId, typeof tokenId); 188 | expect(typeof tokenId).equal("number"); 189 | }); 190 | }); 191 | 192 | async function fetchGasInfo():Promise{ 193 | try{ 194 | const response = await fetch('https://gasstation-mainnet.matic.network/v2') 195 | const gasInfo = await response.json(); 196 | console.log(gasInfo); 197 | 198 | return gasInfo; 199 | }catch(error){ 200 | console.log(error); 201 | throw Error('failed to execute fetchGasInfo()') 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /packages/evm/test/large/vwbl/mintOnAmoy.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import * as dotenv from "dotenv"; 3 | import { Web3 } from "web3"; 4 | import { ethers } from "ethers"; 5 | import { 6 | ManageKeyType, 7 | UploadContentType, 8 | UploadMetadataType, 9 | } from "vwbl-core"; 10 | import { VWBL } from "../../../src/vwbl"; 11 | import HDWalletProvider from "@truffle/hdwallet-provider"; 12 | import * as FileAPI from "file-api"; 13 | const File = FileAPI.File; 14 | dotenv.config(); 15 | 16 | type GasInfo = { 17 | safeLow: { maxPriorityFee: number; maxFee: number }; 18 | standard: { maxPriorityFee: number; maxFee: number }; 19 | fast: { maxPriorityFee: number; maxFee: number }; 20 | estimatedBaseFee: number; 21 | blockTime: number; 22 | blockNumber: number; 23 | }; 24 | const providerUrl = "https://rpc-amoy.polygon.technology/"; 25 | const nftContractAddr = "0x5af2D607242f604C8f5e04e8B648741EE59ac847"; // amoy 26 | const networkUrl = "https://dev.vwbl.network/"; 27 | // preparation for web3.js 28 | const hdWalletProvider = new HDWalletProvider({ 29 | privateKeys: [process.env.PRIVATE_KEY as string], 30 | providerOrUrl: providerUrl, 31 | }); 32 | const web3 = new Web3(hdWalletProvider as any); 33 | // preparation for ethers.js 34 | const privateKey = process.env.PRIVATE_KEY as string; 35 | const ethProvider = new ethers.providers.JsonRpcProvider(providerUrl); 36 | const ethSigner = new ethers.Wallet(privateKey, ethProvider); 37 | 38 | describe("VWBL with web3.js", () => { 39 | const vwbl = new VWBL({ 40 | ipfsConfig: undefined, 41 | awsConfig: undefined, 42 | contractAddress: nftContractAddr, 43 | manageKeyType: ManageKeyType.VWBL_NETWORK_SERVER, 44 | uploadContentType: UploadContentType.IPFS, 45 | uploadMetadataType: UploadMetadataType.IPFS, 46 | vwblNetworkUrl: networkUrl, 47 | web3, 48 | }); 49 | 50 | const testSubscriber = { 51 | kickStep: () => {}, 52 | }; 53 | 54 | it("mint token with gas settings", async () => { 55 | await vwbl.sign(); 56 | 57 | const gasInfo = await fetchGasInfo(); 58 | if (!gasInfo) { 59 | throw Error("failed to fetch gas information about polygon"); 60 | } 61 | console.log(gasInfo.standard); 62 | const maxPriorityFee_wei = Number( 63 | web3.utils.toWei( 64 | String(gasInfo.standard.maxPriorityFee.toFixed(9)), 65 | "gwei" 66 | ) 67 | ); 68 | const maxFee_wei = Number( 69 | web3.utils.toWei(String(gasInfo.standard.maxFee.toFixed(9)), "gwei") 70 | ); 71 | 72 | const tokenId = await vwbl.managedCreateTokenForIPFS( 73 | "test token", 74 | "test", 75 | new File({ 76 | name: "thumbnail image", 77 | type: "image/png", 78 | buffer: Buffer.alloc(100), 79 | }), 80 | new File({ 81 | name: "plain data", 82 | type: "image/png", 83 | buffer: Buffer.alloc(100), 84 | }), 85 | 10, 86 | "base64", 87 | undefined, 88 | undefined, 89 | undefined, 90 | testSubscriber, 91 | { maxPriorityFeePerGas: maxPriorityFee_wei, maxFeePerGas: maxFee_wei } 92 | ); 93 | console.log(tokenId, typeof tokenId); 94 | expect(typeof tokenId).equal("string"); //WARNING:The return value type for 'tokenId' is a string. 95 | }); 96 | 97 | it("mint token without gas settings", async () => { 98 | await vwbl.sign(); 99 | 100 | const tokenId = await vwbl.managedCreateTokenForIPFS( 101 | "test token", 102 | "test", 103 | new File({ 104 | name: "thumbnail image", 105 | type: "image/png", 106 | buffer: Buffer.alloc(100), 107 | }), 108 | new File({ 109 | name: "plain data", 110 | type: "image/png", 111 | buffer: Buffer.alloc(100), 112 | }), 113 | 10, 114 | "base64" 115 | ); 116 | console.log(tokenId, typeof tokenId); 117 | expect(typeof tokenId).equal("string"); //WARNING:The return value type for 'tokenId' is a string. 118 | }); 119 | }); 120 | 121 | describe("VWBL with ethers.js", () => { 122 | const vwbl = new VWBL({ 123 | ipfsConfig: undefined, 124 | awsConfig: undefined, 125 | contractAddress: nftContractAddr, 126 | manageKeyType: ManageKeyType.VWBL_NETWORK_SERVER, 127 | uploadContentType: UploadContentType.IPFS, 128 | uploadMetadataType: UploadMetadataType.IPFS, 129 | vwblNetworkUrl: networkUrl, 130 | ethersProvider: ethProvider, 131 | ethersSigner: ethSigner, 132 | }); 133 | 134 | const testSubscriber = { 135 | kickStep: () => {}, 136 | }; 137 | 138 | it("mint token with gas settings", async () => { 139 | await vwbl.sign(); 140 | 141 | const gasInfo = await fetchGasInfo(); 142 | if (!gasInfo) { 143 | throw Error("failed to fetch gas information about polygon"); 144 | } 145 | console.log(gasInfo.standard); 146 | const maxPriorityFee_wei = Number( 147 | web3.utils.toWei( 148 | String(gasInfo.standard.maxPriorityFee.toFixed(9)), 149 | "gwei" 150 | ) 151 | ); 152 | const maxFee_wei = Number( 153 | web3.utils.toWei(String(gasInfo.standard.maxFee.toFixed(9)), "gwei") 154 | ); 155 | 156 | const tokenId = await vwbl.managedCreateTokenForIPFS( 157 | "test token", 158 | "test", 159 | new File({ 160 | name: "thumbnail image", 161 | type: "image/png", 162 | buffer: Buffer.alloc(100), 163 | }), 164 | new File({ 165 | name: "plain data", 166 | type: "image/png", 167 | buffer: Buffer.alloc(100), 168 | }), 169 | 10, 170 | "base64", 171 | undefined, 172 | undefined, 173 | undefined, 174 | testSubscriber, 175 | { maxPriorityFeePerGas: maxPriorityFee_wei, maxFeePerGas: maxFee_wei } 176 | ); 177 | console.log(tokenId, typeof tokenId); 178 | expect(typeof tokenId).equal("number"); 179 | }); 180 | 181 | it("mint token without gas settings", async () => { 182 | await vwbl.sign(); 183 | 184 | const tokenId = await vwbl.managedCreateTokenForIPFS( 185 | "test token", 186 | "test", 187 | new File({ 188 | name: "thumbnail image", 189 | type: "image/png", 190 | buffer: Buffer.alloc(100), 191 | }), 192 | new File({ 193 | name: "plain data", 194 | type: "image/png", 195 | buffer: Buffer.alloc(100), 196 | }), 197 | 10, 198 | "base64" 199 | ); 200 | console.log(tokenId, typeof tokenId); 201 | expect(typeof tokenId).equal("number"); 202 | }); 203 | }); 204 | 205 | async function fetchGasInfo(): Promise { 206 | try { 207 | const response = await fetch("https://gasstation-mainnet.matic.network/v2"); 208 | const gasInfo = await response.json(); 209 | console.log(gasInfo); 210 | 211 | return gasInfo; 212 | } catch (error) { 213 | console.log(error); 214 | throw Error("failed to execute fetchGasInfo()"); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /packages/evm/src/contract/Forwarder.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"domainSeparator","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"domainValue","type":"bytes"}],"name":"DomainRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"userAddress","type":"address"},{"indexed":true,"internalType":"address","name":"relayerAddress","type":"address"},{"indexed":true,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"CUSTOM_FORWARD_REQUEST_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EIP712_DOMAIN_TYPE","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FORWARD_REQUEST_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"domains","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"txGas","type":"uint256"},{"internalType":"uint256","name":"tokenGasPrice","type":"uint256"},{"internalType":"uint256","name":"batchId","type":"uint256"},{"internalType":"uint256","name":"batchNonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ForwardRequestTypesV2.ERC20ForwardRequest","name":"req","type":"tuple"},{"internalType":"bytes32","name":"domainSeparator","type":"bytes32"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"executeEIP712","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"ret","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"warning","type":"string"},{"internalType":"string","name":"info","type":"string"},{"internalType":"string","name":"action","type":"string"},{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"txGas","type":"uint256"},{"internalType":"uint256","name":"tokenGasPrice","type":"uint256"},{"internalType":"uint256","name":"batchId","type":"uint256"},{"internalType":"uint256","name":"batchNonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ForwardRequestTypesV2.ERC20ForwardRequest","name":"request","type":"tuple"}],"internalType":"struct ForwardRequestTypesV2.CustomForwardRequest","name":"req","type":"tuple"},{"internalType":"bytes32","name":"domainSeparator","type":"bytes32"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"executeEIP712Custom","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"ret","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"txGas","type":"uint256"},{"internalType":"uint256","name":"tokenGasPrice","type":"uint256"},{"internalType":"uint256","name":"batchId","type":"uint256"},{"internalType":"uint256","name":"batchNonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ForwardRequestTypesV2.ERC20ForwardRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"executePersonalSign","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"ret","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"batchId","type":"uint256"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"}],"name":"registerDomainSeparator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"txGas","type":"uint256"},{"internalType":"uint256","name":"tokenGasPrice","type":"uint256"},{"internalType":"uint256","name":"batchId","type":"uint256"},{"internalType":"uint256","name":"batchNonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ForwardRequestTypesV2.ERC20ForwardRequest","name":"req","type":"tuple"},{"internalType":"bytes32","name":"domainSeparator","type":"bytes32"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"verifyEIP712","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"txGas","type":"uint256"},{"internalType":"uint256","name":"tokenGasPrice","type":"uint256"},{"internalType":"uint256","name":"batchId","type":"uint256"},{"internalType":"uint256","name":"batchNonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ForwardRequestTypesV2.ERC20ForwardRequest","name":"req","type":"tuple"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"verifyPersonalSign","outputs":[],"stateMutability":"view","type":"function"}] 3 | } -------------------------------------------------------------------------------- /packages/evm/test/large/vwbl/mintOnSepolia.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import * as dotenv from "dotenv"; 3 | import { Web3 } from "web3"; 4 | import { ethers } from "ethers"; 5 | import { 6 | ManageKeyType, 7 | UploadContentType, 8 | UploadMetadataType, 9 | } from "vwbl-core"; 10 | import { VWBL } from "../../../src/vwbl"; 11 | import HDWalletProvider from "@truffle/hdwallet-provider"; 12 | import * as FileAPI from "file-api"; 13 | const File = FileAPI.File; 14 | dotenv.config(); 15 | 16 | const providerUrl = process.env.SEPOLIA_PROVIDER_URL; 17 | const nftContractAddr = process.env.SEPOLIA_NFT_CONTRACT as string; 18 | const networkUrl = "https://dev.vwbl.network/"; 19 | // preparation for web3.js 20 | const hdWalletProvider = new HDWalletProvider({ 21 | privateKeys: [process.env.PRIVATE_KEY as string], 22 | providerOrUrl: providerUrl, 23 | }); 24 | const web3 = new Web3(hdWalletProvider as any); 25 | // preparation for ethers.js 26 | const privateKey = process.env.PRIVATE_KEY as string; 27 | const ethProvider = new ethers.providers.JsonRpcProvider(providerUrl); 28 | const ethSigner = new ethers.Wallet(privateKey, ethProvider); 29 | const maxPriorityFee_gwei = "1.5"; 30 | const maxFee_gwei = "47.329387804"; 31 | 32 | describe("VWBL with web3.js", () => { 33 | const vwbl = new VWBL({ 34 | ipfsConfig: undefined, 35 | awsConfig: undefined, 36 | contractAddress: nftContractAddr, 37 | manageKeyType: ManageKeyType.VWBL_NETWORK_SERVER, 38 | uploadContentType: UploadContentType.IPFS, 39 | uploadMetadataType: UploadMetadataType.IPFS, 40 | vwblNetworkUrl: networkUrl, 41 | web3, 42 | }); 43 | 44 | const testSubscriber = { 45 | kickStep: () => {}, 46 | }; 47 | 48 | it("mint token with maxPriorityFee and maxFee", async () => { 49 | await vwbl.sign(); 50 | 51 | const maxPriorityFee_wei = Number( 52 | web3.utils.toWei(maxPriorityFee_gwei, "gwei") 53 | ); 54 | const maxFee_wei = Number(web3.utils.toWei(maxFee_gwei, "gwei")); 55 | 56 | const tokenId = await vwbl.managedCreateTokenForIPFS( 57 | "test token", 58 | "test", 59 | new File({ 60 | name: "thumbnail image", 61 | type: "image/png", 62 | buffer: Buffer.alloc(100), 63 | }), 64 | new File({ 65 | name: "plain data", 66 | type: "image/png", 67 | buffer: Buffer.alloc(100), 68 | }), 69 | 10, 70 | "base64", 71 | undefined, 72 | undefined, 73 | undefined, 74 | testSubscriber, 75 | { maxPriorityFeePerGas: maxPriorityFee_wei, maxFeePerGas: maxFee_wei } 76 | ); 77 | console.log(tokenId, typeof tokenId); 78 | expect(typeof tokenId).equal("string"); //WARNING:The return value type for 'tokenId' is a string. 79 | }); 80 | 81 | it("mint token with gasPrice", async () => { 82 | await vwbl.sign(); 83 | 84 | const gasPrice = Number(await web3.eth.getGasPrice()); 85 | 86 | const tokenId = await vwbl.managedCreateTokenForIPFS( 87 | "test token", 88 | "test", 89 | new File({ 90 | name: "thumbnail image", 91 | type: "image/png", 92 | buffer: Buffer.alloc(100), 93 | }), 94 | new File({ 95 | name: "plain data", 96 | type: "image/png", 97 | buffer: Buffer.alloc(100), 98 | }), 99 | 10, 100 | "base64", 101 | undefined, 102 | undefined, 103 | undefined, 104 | testSubscriber, 105 | { gasPrice } 106 | ); 107 | console.log(tokenId, typeof tokenId); 108 | expect(typeof tokenId).equal("string"); //WARNING:The return value type for 'tokenId' is a string. 109 | }); 110 | 111 | it("mint token without gas settings", async () => { 112 | await vwbl.sign(); 113 | 114 | const tokenId = await vwbl.managedCreateTokenForIPFS( 115 | "test token", 116 | "test", 117 | new File({ 118 | name: "thumbnail image", 119 | type: "image/png", 120 | buffer: Buffer.alloc(100), 121 | }), 122 | new File({ 123 | name: "plain data", 124 | type: "image/png", 125 | buffer: Buffer.alloc(100), 126 | }), 127 | 10, 128 | "base64" 129 | ); 130 | console.log(tokenId, typeof tokenId); 131 | expect(typeof tokenId).equal("string"); //WARNING:The return value type for 'tokenId' is a string. 132 | }); 133 | }); 134 | 135 | describe("VWBL with ethers.js", () => { 136 | const vwbl = new VWBL({ 137 | ipfsConfig: undefined, 138 | awsConfig: undefined, 139 | contractAddress: nftContractAddr, 140 | manageKeyType: ManageKeyType.VWBL_NETWORK_SERVER, 141 | uploadContentType: UploadContentType.IPFS, 142 | uploadMetadataType: UploadMetadataType.IPFS, 143 | vwblNetworkUrl: networkUrl, 144 | ethersProvider: ethProvider, 145 | ethersSigner: ethSigner, 146 | }); 147 | 148 | const testSubscriber = { 149 | kickStep: () => {}, 150 | }; 151 | 152 | it("mint token with maxPriorityFee and maxFee", async () => { 153 | await vwbl.sign(); 154 | 155 | const maxPriorityFee_wei = Number( 156 | web3.utils.toWei(maxPriorityFee_gwei, "gwei") 157 | ); 158 | const maxFee_wei = Number(web3.utils.toWei(maxFee_gwei, "gwei")); 159 | 160 | const tokenId = await vwbl.managedCreateTokenForIPFS( 161 | "test token", 162 | "test", 163 | new File({ 164 | name: "thumbnail image", 165 | type: "image/png", 166 | buffer: Buffer.alloc(100), 167 | }), 168 | new File({ 169 | name: "plain data", 170 | type: "image/png", 171 | buffer: Buffer.alloc(100), 172 | }), 173 | 10, 174 | "base64", 175 | undefined, 176 | undefined, 177 | undefined, 178 | testSubscriber, 179 | { maxPriorityFeePerGas: maxPriorityFee_wei, maxFeePerGas: maxFee_wei } 180 | ); 181 | console.log(tokenId, typeof tokenId); 182 | expect(typeof tokenId).equal("number"); 183 | }); 184 | 185 | it("mint token with gasPrice", async () => { 186 | await vwbl.sign(); 187 | 188 | const gasPrice = Number(await web3.eth.getGasPrice()); 189 | 190 | const tokenId = await vwbl.managedCreateTokenForIPFS( 191 | "test token", 192 | "test", 193 | new File({ 194 | name: "thumbnail image", 195 | type: "image/png", 196 | buffer: Buffer.alloc(100), 197 | }), 198 | new File({ 199 | name: "plain data", 200 | type: "image/png", 201 | buffer: Buffer.alloc(100), 202 | }), 203 | 10, 204 | "base64", 205 | undefined, 206 | undefined, 207 | undefined, 208 | testSubscriber, 209 | { gasPrice } 210 | ); 211 | console.log(tokenId, typeof tokenId); 212 | expect(typeof tokenId).equal("number"); 213 | }); 214 | 215 | it("mint token without gas settings", async () => { 216 | await vwbl.sign(); 217 | 218 | const tokenId = await vwbl.managedCreateTokenForIPFS( 219 | "test token", 220 | "test", 221 | new File({ 222 | name: "thumbnail image", 223 | type: "image/png", 224 | buffer: Buffer.alloc(100), 225 | }), 226 | new File({ 227 | name: "plain data", 228 | type: "image/png", 229 | buffer: Buffer.alloc(100), 230 | }), 231 | 10, 232 | "base64" 233 | ); 234 | console.log(tokenId, typeof tokenId); 235 | expect(typeof tokenId).equal("number"); 236 | }); 237 | }); 238 | -------------------------------------------------------------------------------- /packages/evm/test/large/vwbl/mintOnGoerli.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import * as dotenv from "dotenv"; 3 | import { Web3 } from "web3"; 4 | import { ethers } from "ethers"; 5 | // eslint-disable-next-line @typescript-eslint/no-var-requires 6 | import { 7 | ManageKeyType, 8 | UploadContentType, 9 | UploadMetadataType, 10 | } from "vwbl-core"; 11 | import { VWBL } from "../../../src/vwbl"; 12 | import HDWalletProvider from "@truffle/hdwallet-provider"; 13 | import * as FileAPI from "file-api"; 14 | const File = FileAPI.File; 15 | dotenv.config(); 16 | 17 | const providerUrl = process.env.GOERLI_PROVIDER_URL; 18 | const nftContractAddr = process.env.GOERLI_NFT_CONTRACT as string; 19 | const networkUrl = "https://dev.vwbl.network/"; 20 | // preparation for web3.js 21 | const hdWalletProvider = new HDWalletProvider({ 22 | privateKeys: [process.env.PRIVATE_KEY as string], 23 | providerOrUrl: providerUrl 24 | }) 25 | const web3 = new Web3(hdWalletProvider as any); 26 | // preparation for ethers.js 27 | const privateKey = process.env.PRIVATE_KEY as string; 28 | const ethProvider = new ethers.providers.JsonRpcProvider(providerUrl); 29 | const ethSigner = new ethers.Wallet(privateKey, ethProvider); 30 | const maxPriorityFee_gwei = '1.5'; 31 | const maxFee_gwei = '47.329387804'; 32 | 33 | describe("VWBL with web3.js", () => { 34 | const vwbl = new VWBL({ 35 | ipfsConfig: undefined, 36 | awsConfig: undefined, 37 | contractAddress: nftContractAddr, 38 | manageKeyType: ManageKeyType.VWBL_NETWORK_SERVER, 39 | uploadContentType: UploadContentType.IPFS, 40 | uploadMetadataType: UploadMetadataType.IPFS, 41 | vwblNetworkUrl: networkUrl, 42 | web3, 43 | }); 44 | 45 | const testSubscriber = { 46 | kickStep: () => {}, 47 | }; 48 | 49 | it.skip("mint token with maxPriorityFee and maxFee", async () => { 50 | await vwbl.sign(); 51 | 52 | const maxPriorityFee_wei = Number(web3.utils.toWei(maxPriorityFee_gwei, 'gwei')); 53 | const maxFee_wei = Number(web3.utils.toWei(maxFee_gwei, 'gwei')); 54 | 55 | const tokenId = await vwbl.managedCreateTokenForIPFS( 56 | "test token", 57 | "test", 58 | new File({ 59 | name: "thumbnail image", 60 | type: "image/png", 61 | buffer: Buffer.alloc(100), 62 | }), 63 | new File({ 64 | name: "plain data", 65 | type: "image/png", 66 | buffer: Buffer.alloc(100), 67 | }), 68 | 10, 69 | "base64", 70 | undefined, 71 | undefined, 72 | undefined, 73 | testSubscriber, 74 | { maxPriorityFeePerGas: maxPriorityFee_wei, maxFeePerGas: maxFee_wei } 75 | ); 76 | console.log(tokenId, typeof tokenId); 77 | expect(typeof tokenId).equal("string"); //WARNING:The return value type for 'tokenId' is a string. 78 | }); 79 | 80 | it("mint token with gasPrice", async () => { 81 | await vwbl.sign(); 82 | 83 | const gasPrice = Number(await web3.eth.getGasPrice()); 84 | 85 | const tokenId = await vwbl.managedCreateTokenForIPFS( 86 | "test token", 87 | "test", 88 | new File({ 89 | name: "thumbnail image", 90 | type: "image/png", 91 | buffer: Buffer.alloc(100), 92 | }), 93 | new File({ 94 | name: "plain data", 95 | type: "image/png", 96 | buffer: Buffer.alloc(100), 97 | }), 98 | 10, 99 | "base64", 100 | undefined, 101 | undefined, 102 | undefined, 103 | testSubscriber, 104 | { gasPrice } 105 | ); 106 | console.log(tokenId, typeof tokenId); 107 | expect(typeof tokenId).equal("string"); //WARNING:The return value type for 'tokenId' is a string. 108 | }); 109 | 110 | it.skip("mint token without gas settings", async () => { 111 | await vwbl.sign(); 112 | 113 | const tokenId = await vwbl.managedCreateTokenForIPFS( 114 | "test token", 115 | "test", 116 | new File({ 117 | name: "thumbnail image", 118 | type: "image/png", 119 | buffer: Buffer.alloc(100), 120 | }), 121 | new File({ 122 | name: "plain data", 123 | type: "image/png", 124 | buffer: Buffer.alloc(100), 125 | }), 126 | 10, 127 | "base64", 128 | ); 129 | console.log(tokenId, typeof tokenId); 130 | expect(typeof tokenId).equal("string"); //WARNING:The return value type for 'tokenId' is a string. 131 | }); 132 | }); 133 | 134 | describe("VWBL with ethers.js", () => { 135 | const vwbl = new VWBL({ 136 | ipfsConfig: undefined, 137 | awsConfig: undefined, 138 | contractAddress: nftContractAddr, 139 | manageKeyType: ManageKeyType.VWBL_NETWORK_SERVER, 140 | uploadContentType: UploadContentType.IPFS, 141 | uploadMetadataType: UploadMetadataType.IPFS, 142 | vwblNetworkUrl: networkUrl, 143 | ethersProvider: ethProvider, 144 | ethersSigner: ethSigner, 145 | }); 146 | 147 | const testSubscriber = { 148 | kickStep: () => {}, 149 | }; 150 | 151 | it.skip("mint token with maxPriorityFee and maxFee", async () => { 152 | await vwbl.sign(); 153 | 154 | const maxPriorityFee_wei = Number(web3.utils.toWei(maxPriorityFee_gwei, 'gwei')); 155 | const maxFee_wei = Number(web3.utils.toWei(maxFee_gwei, 'gwei')); 156 | 157 | const tokenId = await vwbl.managedCreateTokenForIPFS( 158 | "test token", 159 | "test", 160 | new File({ 161 | name: "thumbnail image", 162 | type: "image/png", 163 | buffer: Buffer.alloc(100), 164 | }), 165 | new File({ 166 | name: "plain data", 167 | type: "image/png", 168 | buffer: Buffer.alloc(100), 169 | }), 170 | 10, 171 | "base64", 172 | undefined, 173 | undefined, 174 | undefined, 175 | testSubscriber, 176 | { maxPriorityFeePerGas: maxPriorityFee_wei, maxFeePerGas: maxFee_wei } 177 | ); 178 | console.log(tokenId, typeof tokenId); 179 | expect(typeof tokenId).equal("number"); 180 | }); 181 | 182 | it("mint token with gasPrice", async () => { 183 | await vwbl.sign(); 184 | 185 | const gasPrice = Number(await web3.eth.getGasPrice()); 186 | 187 | const tokenId = await vwbl.managedCreateTokenForIPFS( 188 | "test token", 189 | "test", 190 | new File({ 191 | name: "thumbnail image", 192 | type: "image/png", 193 | buffer: Buffer.alloc(100), 194 | }), 195 | new File({ 196 | name: "plain data", 197 | type: "image/png", 198 | buffer: Buffer.alloc(100), 199 | }), 200 | 10, 201 | "base64", 202 | undefined, 203 | undefined, 204 | undefined, 205 | testSubscriber, 206 | { gasPrice } 207 | ); 208 | console.log(tokenId, typeof tokenId); 209 | expect(typeof tokenId).equal("number"); 210 | }); 211 | 212 | it.skip("mint token without gas settings", async () => { 213 | await vwbl.sign(); 214 | 215 | const tokenId = await vwbl.managedCreateTokenForIPFS( 216 | "test token", 217 | "test", 218 | new File({ 219 | name: "thumbnail image", 220 | type: "image/png", 221 | buffer: Buffer.alloc(100), 222 | }), 223 | new File({ 224 | name: "plain data", 225 | type: "image/png", 226 | buffer: Buffer.alloc(100), 227 | }), 228 | 10, 229 | "base64", 230 | ); 231 | console.log(tokenId, typeof tokenId); 232 | expect(typeof tokenId).equal("number"); 233 | }); 234 | }); 235 | -------------------------------------------------------------------------------- /packages/core/src/storage/ipfs/upload.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { Buffer } from "buffer"; 3 | import FormData from "form-data"; 4 | import fs from "fs"; 5 | import { Readable } from "stream"; 6 | 7 | import { isRunningOnBrowser, isRunningOnNode } from "../../util"; 8 | import { IPFSConfig } from "./types"; 9 | 10 | const pinataEndpoint = "https://api.pinata.cloud/pinning/pinFileToIPFS"; 11 | 12 | const createHeaders = (ipfsConfig: IPFSConfig): { [key: string]: string } => { 13 | const headers: { [key: string]: string } = { 14 | pinata_api_key: ipfsConfig.apiKey, 15 | pinata_secret_api_key: ipfsConfig.apiSecret as string, 16 | }; 17 | headers["Content-Type"] = "multipart/form-data"; 18 | return headers; 19 | }; 20 | 21 | const createHeadersOnNode = (ipfsConfig: IPFSConfig, formData: FormData): { [key: string]: any } => { // eslint-disable-line @typescript-eslint/no-explicit-any 22 | const headers: { [key: string]: any } = { // eslint-disable-line @typescript-eslint/no-explicit-any 23 | pinata_api_key: ipfsConfig.apiKey, 24 | pinata_secret_api_key: ipfsConfig.apiSecret as string, 25 | }; 26 | Object.assign(headers, formData.getHeaders()); 27 | return headers; 28 | }; 29 | 30 | type ConfigType = { 31 | headers: { 32 | [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any 33 | }; 34 | onUploadProgress: ((progressEvent: any) => void) | undefined; // eslint-disable-line @typescript-eslint/no-explicit-any 35 | }; 36 | 37 | const createConfig = ( 38 | headers: { [key: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any 39 | progressType: string 40 | ): ConfigType => { 41 | return { 42 | headers: headers, 43 | onUploadProgress: isRunningOnBrowser() 44 | ? (progressEvent: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any 45 | const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total); 46 | console.log(`${progressType} Progress: ${progress}%`); 47 | } 48 | : undefined, 49 | }; 50 | }; 51 | 52 | const uploadFile = async (formData: FormData | globalThis.FormData, config: ConfigType): Promise => { 53 | try { 54 | const response = await axios.post(pinataEndpoint, formData, config); 55 | return `https://gateway.pinata.cloud/ipfs/${response.data.IpfsHash}`; 56 | } catch (err: any) { // eslint-disable-line @typescript-eslint/no-explicit-any 57 | throw new Error(`Pinata upload failed: ${err.message}`); 58 | } 59 | }; 60 | 61 | // Pinata Authentication Test Functions 62 | export const testPinataAuthentication = async (ipfsConfig: IPFSConfig): Promise => { 63 | const headers = createHeaders(ipfsConfig); 64 | const config = { 65 | headers: headers, 66 | }; 67 | 68 | try { 69 | const response = await axios.get("https://api.pinata.cloud/data/testAuthentication", config); 70 | console.log("Pinata authentication succeeded:", response.data); 71 | } catch (err: any) { // eslint-disable-line @typescript-eslint/no-explicit-any 72 | console.error("Pinata authentication failed:", headers); 73 | throw new Error(`Pinata authentication failed: ${err.message}`); 74 | } 75 | }; 76 | 77 | // Upload function for encrypted files 78 | export const uploadEncryptedFileToIPFS = async ( 79 | encryptedContent: string | Uint8Array | Readable, 80 | ipfsConfig?: IPFSConfig 81 | ): Promise => { 82 | if (!ipfsConfig || !ipfsConfig.apiKey || !ipfsConfig.apiSecret) { 83 | throw new Error("Pinata API key or secret is not specified."); 84 | } 85 | 86 | let formData: FormData | globalThis.FormData; 87 | let headers: { [key: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any 88 | if (typeof encryptedContent === "string" || encryptedContent instanceof Uint8Array) { 89 | if (isRunningOnNode()) { 90 | formData = new FormData(); 91 | const blob = Buffer.from(encryptedContent); 92 | formData.append("file", blob, "encrypted-file"); 93 | headers = createHeadersOnNode(ipfsConfig, formData); 94 | } else { 95 | formData = new window.FormData(); 96 | const blob = new Blob([encryptedContent], { 97 | type: "application/octet-stream", 98 | }); 99 | formData.append("file", blob, "encrypted-file"); 100 | headers = createHeaders(ipfsConfig); 101 | } 102 | } else { 103 | formData = new FormData(); 104 | formData.append("file", encryptedContent, { filename: "encrypted-file" }); 105 | headers = createHeadersOnNode(ipfsConfig, formData); 106 | } 107 | const config = createConfig(headers, "uploadMetadataToIPFS"); 108 | const encryptedDataUrl = await uploadFile(formData, config); 109 | return encryptedDataUrl; 110 | }; 111 | 112 | // upload function for thumbnailImage 113 | export const uploadThumbnailToIPFS = async ( 114 | thumbnailImage: string | File | Blob, 115 | ipfsConfig?: IPFSConfig 116 | ): Promise => { 117 | if (!ipfsConfig || !ipfsConfig.apiKey || !ipfsConfig.apiSecret) { 118 | throw new Error("Pinata API key or secret is not specified."); 119 | } 120 | 121 | const formData = new FormData(); 122 | let headers: { [key: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any 123 | if (isRunningOnNode()) { 124 | if (typeof thumbnailImage === "string") { 125 | const stream = fs.createReadStream(thumbnailImage); 126 | formData.append("file", stream); 127 | } else if (thumbnailImage instanceof Buffer) { 128 | formData.append("file", thumbnailImage, "thumbnail"); 129 | } else { 130 | throw new Error("Invalid type for thumbnailImage in Node.js environment"); 131 | } 132 | headers = createHeadersOnNode(ipfsConfig, formData); 133 | } else { 134 | if (thumbnailImage instanceof File || thumbnailImage instanceof Blob) { 135 | formData.append("file", thumbnailImage); 136 | } else if (typeof thumbnailImage === "string") { 137 | const response = await fetch(thumbnailImage); 138 | const blob = await response.blob(); 139 | formData.append("file", new File([blob], "thumbnail", { type: blob.type })); 140 | } else { 141 | throw new Error("Invalid type for thumbnailImage in browser environment"); 142 | } 143 | headers = createHeaders(ipfsConfig); 144 | } 145 | const config = createConfig(headers, "uploadThumbnailToIPFS"); 146 | const thumbnailImageUrl = await uploadFile(formData, config); 147 | return thumbnailImageUrl; 148 | }; 149 | 150 | // upload function for metadata 151 | export const uploadMetadataToIPFS = async ( 152 | name: string, 153 | description: string, 154 | previewImageUrl: string, 155 | encryptedDataUrls: string[], 156 | mimeType: string, 157 | encryptLogic: string, 158 | ipfsConfig?: IPFSConfig 159 | ): Promise => { 160 | if (!ipfsConfig || !ipfsConfig.apiKey || !ipfsConfig.apiSecret) { 161 | throw new Error("Pinata API key or secret is not specified."); 162 | } 163 | 164 | const metadata = { 165 | name, 166 | description, 167 | image: previewImageUrl, 168 | encrypted_data: encryptedDataUrls, 169 | mime_type: mimeType, 170 | encrypt_logic: encryptLogic, 171 | }; 172 | 173 | const metadataJSON = JSON.stringify(metadata); 174 | const formData = new FormData(); 175 | let headers: { [key: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any 176 | if (isRunningOnNode()) { 177 | formData.append("file", Buffer.from(metadataJSON), { 178 | filename: "metadata.json", 179 | contentType: "application/json", 180 | }); 181 | headers = createHeadersOnNode(ipfsConfig, formData); 182 | } else { 183 | const blob = new Blob([metadataJSON], { type: "application/json" }); 184 | formData.append("file", blob, "metadata.json"); 185 | headers = createHeaders(ipfsConfig); 186 | } 187 | const config = createConfig(headers, "uploadMetadataToIPFS"); 188 | const metadataUrl = await uploadFile(formData, config); 189 | return metadataUrl; 190 | }; 191 | -------------------------------------------------------------------------------- /packages/evm/README.md: -------------------------------------------------------------------------------- 1 | # VWBL SDK for EVM Compatible Chain 2 | 3 | [![npm version](https://badge.fury.io/js/vwbl-sdk.svg)](https://badge.fury.io/js/vwbl-sdk) [![npm download](https://img.shields.io/npm/dt/vwbl-sdk.svg)](https://img.shields.io/npm/dt/vwbl-sdk.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | ## official document 6 | 7 | https://docs.vwbl-protocol.org 8 | 9 | ### install 10 | 11 | ##### Using NPM 12 | `npm install vwbl-sdk` 13 | 14 | ##### Using Yarn 15 | `yarn add vwbl-sdk` 16 | 17 | ### Build 18 | 19 | `npm run build:evm` 20 | 21 | ### Test 22 | `npm run test:evm` 23 | 24 | ## api document 25 | 26 | ### create instance 27 | 28 | ```typescript 29 | new VWBL({ 30 | web3, 31 | contractAddress: "0x...", 32 | manageKeyType: ManageKeyType.VWBL_NETWORK_SERVER, 33 | uploadContentType: UploadContentType.S3, 34 | uploadMetadataType: UploadMetadataType.S3, 35 | vwblNetworkUrl: "https://vwbl.network", 36 | awsConfig: { 37 | region: "ap-northeast-1", 38 | idPoolId: "ap-northeast-1:...", 39 | cloudFrontUrl: "https://xxx.cloudfront.net", 40 | bucketName: { 41 | metadata: "vwbl-metadata", 42 | content: "vwbl-content", 43 | }, 44 | }, 45 | }); 46 | ``` 47 | 48 | Constructor Options 49 | 50 | | name | required | type | description | 51 | | ------------------ | -------------------------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | 52 | | web3 | true | [Web3](https://www.npmjs.com/package/web3) | web3 instance | 53 | | contractAddress | true | string | VWBL nft's contract address | 54 | | vwblNetworkUrl | true | string | VWBL network's url | 55 | | manageKeyType | false | ManageKeyType | how to manage key, you can choose from
VWBL_NETWORK_SERVER
VWBL_NETWORK_CONSORTIUM(not implemented yet)
MY_SERVER(not implemented yet). | 56 | | uploadContentType | flase | UploadContentType | where to upload content, you can choose from
S3
IPFS
CUSTOM | 57 | | uploadMetadataType | flase | UploadMetadataType | where to upload content, you can choose from
S3
IPFS
CUSTOM | 58 | | awsConfig | true if you choose to upload content or metadata to S3 | AWSConfig | AWSConfig \*1 | 59 | | ipfsConfig | true if you choose to upload content or metadata to IPFS | IPFSConfig | IPFSConfig \*2 | 60 | 61 | AWSConfig(*1) 62 | 63 | | name | required | type | description | 64 | | ------------- | -------- | ----------------------------------- | --------------------------------------------------------- | 65 | | region | true | string | AWS region | 66 | | idPoolId | true | string | idPoolId which has granted S3-put-object | 67 | | cloudFrontUrl | true | string | cloudFront url connect to s3 which is uploaded content | 68 | | bucketName | true | {content: string, metadata: string} | bucketName of metadata and content, it's ok they are same | 69 | 70 | 71 | IPFSConfig(*2) 72 | 73 | | name | required | type | description | 74 | | ------------- | -------- | ----------------------------------- | --------------------------------------------------------- | 75 | | apiKey | true | string | API key | 76 | | apiSecret | false | string | API secret key | 77 | | 78 | ### sign to server 79 | 80 | Signing is necessary before creating token or viewing contents. 81 | 82 | ```typescript 83 | await vwbl.sign(); 84 | ``` 85 | 86 | ### create token 87 | 88 | ```typescript 89 | await vwbl.managedCreateToken( 90 | name, 91 | description, 92 | fileContent, 93 | thumbnailContent, 94 | 0 // royaltiesPercentage 95 | ); 96 | ``` 97 | 98 | Arguments 99 | 100 | | name | required | type | description | 101 | | --------------------------- | ------------------------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | 102 | | name | true | string | [ERC721](https://eips.ethereum.org/EIPS/eip-721) metadata name | 103 | | description | true | string | [ERC721](https://eips.ethereum.org/EIPS/eip-721) metadata description | 104 | | plainFile | true | File \| File[] | The data that only NFT owner can view | 105 | | thumbnailImage | true | File | [ERC721](https://eips.ethereum.org/EIPS/eip-721) metadata image | 106 | | royaltiesPercentage | true | number | If the marketplace supports EIP2981, this percentage of the sale price will be paid to the NFT creator every time the NFT is sold or re-sold | 107 | | encryptLogic | false (default="base64") | EncryptLogic | "base64" or "binary". Selection criteria: "base64" -> sutable for small data. "binary" -> sutable for large data. | 108 | | uploadEncryptedFileCallback | true if uploadContentType is CUSTOM | UploadEncryptedFile | you can custom upload function | 109 | | uploadThumbnailCallback | true if uploadContentType is CUSTOM | UploadThumbnail | you can custom upload function | 110 | | uploadMetadataCallback | true if uploadMetadataType is CUSTOM | UploadMetadata | you can custom upload function | 111 | | gasSettings | false | GasSettings | you can custom gas settings | 112 | 113 | ### view contents ( get NFT metadata from given tokenId) 114 | 115 | ```typescript 116 | const token = await vwbl.getTokenById(tokenId); 117 | ``` 118 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/blockchain/erc721/VWBLProtocolEthers.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | import vwbl from "../../../contract/VWBLERC721ERC2981.json"; 4 | import vwblIPFS from "../../../contract/VWBLERC721ERC2981ForMetadata.json"; 5 | import { getFeeSettingsBasedOnEnvironment } from "../../../util/transactionHelper"; 6 | import { GasSettings, GrantViewPermissionTxParam, MintForIPFSTxParam, MintTxParam } from "../../types"; 7 | 8 | export class VWBLNFTEthers { 9 | protected ethersProvider: ethers.providers.BaseProvider; 10 | private ethersSigner: ethers.Signer; 11 | private contract: ethers.Contract; 12 | 13 | constructor( 14 | address: string, 15 | isIpfs: boolean, 16 | ethersProvider: ethers.providers.BaseProvider, 17 | ethersSigner: ethers.Signer 18 | ) { 19 | this.ethersProvider = ethersProvider; 20 | this.ethersSigner = ethersSigner; 21 | this.contract = isIpfs 22 | ? new ethers.Contract(address, vwblIPFS.abi, ethersSigner) 23 | : new ethers.Contract(address, vwbl.abi, ethersSigner); 24 | } 25 | 26 | async mintToken(mintParam: MintTxParam) { 27 | const fee = await this.getFee(); 28 | let txSettings: unknown; 29 | if (mintParam.gasSettings?.gasPrice) { 30 | txSettings = { 31 | value: fee, 32 | gasPrice: mintParam.gasSettings?.gasPrice, 33 | }; 34 | } else { 35 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 36 | getFeeSettingsBasedOnEnvironment( 37 | mintParam.gasSettings?.maxPriorityFeePerGas, 38 | mintParam.gasSettings?.maxFeePerGas 39 | ); 40 | txSettings = { 41 | value: fee, 42 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 43 | maxFeePerGas: _maxFeePerGas, 44 | }; 45 | } 46 | console.log("transaction start"); 47 | const tx = await this.contract.mint(mintParam.decryptUrl, mintParam.feeNumerator, mintParam.documentId, txSettings); 48 | const receipt = await this.ethersProvider.waitForTransaction(tx.hash); 49 | console.log("transaction end"); 50 | const tokenId = parseToTokenId(receipt); 51 | return tokenId; 52 | } 53 | 54 | async mintTokenForIPFS(mintForIPFSParam: MintForIPFSTxParam) { 55 | const fee = await this.getFee(); 56 | let txSettings: unknown; 57 | if (mintForIPFSParam.gasSettings?.gasPrice) { 58 | txSettings = { 59 | value: fee, 60 | gasPrice: mintForIPFSParam.gasSettings?.gasPrice, 61 | }; 62 | } else { 63 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 64 | getFeeSettingsBasedOnEnvironment( 65 | mintForIPFSParam.gasSettings?.maxPriorityFeePerGas, 66 | mintForIPFSParam.gasSettings?.maxFeePerGas 67 | ); 68 | txSettings = { 69 | value: fee, 70 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 71 | maxFeePerGas: _maxFeePerGas, 72 | }; 73 | } 74 | console.log("transaction start"); 75 | const tx = await this.contract.mint( 76 | mintForIPFSParam.metadataUrl, 77 | mintForIPFSParam.decryptUrl, 78 | mintForIPFSParam.feeNumerator, 79 | mintForIPFSParam.documentId, 80 | txSettings 81 | ); 82 | const receipt = await this.ethersProvider.waitForTransaction(tx.hash); 83 | console.log("transaction end"); 84 | const tokenId = parseToTokenId(receipt); 85 | return tokenId; 86 | } 87 | 88 | async getOwnTokenIds() { 89 | const myAddress = await this.ethersSigner.getAddress(); 90 | const balance = await this.contract.callStatic.balanceOf(myAddress); 91 | return await Promise.all( 92 | range(Number.parseInt(balance)).map(async (i) => { 93 | const ownTokenId = await this.contract.callStatic.tokenOfOwnerByIndex(myAddress, i); 94 | return Number.parseInt(ownTokenId); 95 | }) 96 | ); 97 | } 98 | 99 | async getTokenByMinter(address: string) { 100 | return await this.contract.callStatic.getTokenByMinter(address); 101 | } 102 | 103 | async getMetadataUrl(tokenId: number) { 104 | return await this.contract.callStatic.tokenURI(tokenId); 105 | } 106 | 107 | async getOwner(tokenId: number) { 108 | return await this.contract.callStatic.ownerOf(tokenId); 109 | } 110 | 111 | async getMinter(tokenId: number) { 112 | return await this.contract.callStatic.getMinter(tokenId); 113 | } 114 | 115 | async checkViewPermission(tokenId: number, user: string) { 116 | return await this.contract.callStatic.checkViewPermission(tokenId, user); 117 | } 118 | 119 | async isOwnerOf(tokenId: number) { 120 | const myAddress = await this.ethersSigner.getAddress(); 121 | const owner = await this.getOwner(tokenId); 122 | return myAddress === owner; 123 | } 124 | 125 | async isMinterOf(tokenId: number) { 126 | const myAddress = await this.ethersSigner.getAddress(); 127 | const minter = await this.getMinter(tokenId); 128 | return myAddress === minter; 129 | } 130 | 131 | async isGranteeOf(tokenId: number) { 132 | const myAddress = await this.ethersSigner.getAddress(); 133 | return await this.checkViewPermission(tokenId, myAddress); 134 | } 135 | 136 | async getFee() { 137 | return await this.contract.callStatic.getFee(); 138 | } 139 | 140 | async getTokenInfo(tokenId: number) { 141 | return await this.contract.callStatic.tokenIdToTokenInfo(tokenId); 142 | } 143 | 144 | async approve(operator: string, tokenId: number, gasSettings?: GasSettings): Promise { 145 | let txSettings: unknown; 146 | if (gasSettings?.gasPrice) { 147 | txSettings = { 148 | gasPrice: gasSettings?.gasPrice, 149 | }; 150 | } else { 151 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 152 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 153 | txSettings = { 154 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 155 | maxFeePerGas: _maxFeePerGas, 156 | }; 157 | } 158 | const tx = await this.contract.approve(operator, tokenId, txSettings); 159 | await this.ethersProvider.waitForTransaction(tx.hash); 160 | } 161 | 162 | async getApproved(tokenId: number): Promise { 163 | return await this.contract.callStatic.getApproved(tokenId); 164 | } 165 | 166 | async setApprovalForAll(operator: string, gasSettings?: GasSettings): Promise { 167 | let txSettings: unknown; 168 | if (gasSettings?.gasPrice) { 169 | txSettings = { 170 | gasPrice: gasSettings?.gasPrice, 171 | }; 172 | } else { 173 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 174 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 175 | txSettings = { 176 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 177 | maxFeePerGas: _maxFeePerGas, 178 | }; 179 | } 180 | const tx = await this.contract.setApprovalForAll(operator, true, txSettings); 181 | await this.ethersProvider.waitForTransaction(tx.hash); 182 | } 183 | 184 | async isApprovedForAll(owner: string, operator: string): Promise { 185 | return await this.contract.callStatic.isApprovedForAll(owner, operator); 186 | } 187 | 188 | async safeTransfer(to: string, tokenId: number, gasSettings?: GasSettings): Promise { 189 | const myAddress = await this.ethersSigner.getAddress(); 190 | let txSettings: unknown; 191 | if (gasSettings?.gasPrice) { 192 | txSettings = { 193 | gasPrice: gasSettings?.gasPrice, 194 | }; 195 | } else { 196 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 197 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 198 | txSettings = { 199 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 200 | maxFeePerGas: _maxFeePerGas, 201 | }; 202 | } 203 | const tx = await this.contract.safeTransferFrom(myAddress, to, tokenId, txSettings); 204 | await this.ethersProvider.waitForTransaction(tx.hash); 205 | } 206 | 207 | async grantViewPermission(grantParam: GrantViewPermissionTxParam): Promise { 208 | let txSettings: unknown; 209 | if (grantParam.gasSettings?.gasPrice) { 210 | txSettings = { 211 | gasPrice: grantParam.gasSettings?.gasPrice, 212 | }; 213 | } else { 214 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 215 | getFeeSettingsBasedOnEnvironment( 216 | grantParam.gasSettings?.maxPriorityFeePerGas, 217 | grantParam.gasSettings?.maxFeePerGas 218 | ); 219 | txSettings = { 220 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 221 | maxFeePerGas: _maxFeePerGas, 222 | }; 223 | } 224 | const tx = await this.contract.grantViewPermission(grantParam.tokenId, grantParam.grantee, txSettings); 225 | await this.ethersProvider.waitForTransaction(tx.hash); 226 | } 227 | 228 | async revokeViewPermission(tokenId: number, revoker: string, gasSettings?: GasSettings): Promise { 229 | let txSettings: unknown; 230 | if (gasSettings?.gasPrice) { 231 | txSettings = { 232 | gasPrice: gasSettings?.gasPrice, 233 | }; 234 | } else { 235 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 236 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 237 | txSettings = { 238 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 239 | maxFeePerGas: _maxFeePerGas, 240 | }; 241 | } 242 | const tx = await this.contract.revokeViewPermission(tokenId, revoker, txSettings); 243 | await this.ethersProvider.waitForTransaction(tx.hash); 244 | } 245 | } 246 | 247 | const range = (length: number) => { 248 | return Array.from(Array(length).keys()); 249 | }; 250 | 251 | export const parseToTokenId = (receipt: ethers.providers.TransactionReceipt): number => { 252 | const eventInterface = new ethers.utils.Interface([ 253 | "event nftDataRegistered(address contractAddress, uint256 tokenId)", 254 | ]); 255 | let tokenId = 0; 256 | receipt.logs.forEach((log) => { 257 | // check whether topic is nftDataRegistered(address contractAddress, uint256 tokenId) 258 | if (log.topics[0] === "0x957e0e652e4d598197f2c5b25940237e404f3899238efb6f64df2377e9aaf36c") { 259 | const description = eventInterface.parseLog({ topics: log.topics, data: log.data }); 260 | tokenId = description.args[1].toNumber(); 261 | } 262 | }); 263 | return tokenId; 264 | }; 265 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es6", 15 | /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 16 | "lib": [ 17 | "es6", 18 | "dom" 19 | ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, 20 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 21 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 22 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 23 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 24 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 25 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 26 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 27 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 28 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 29 | 30 | /* Modules */ 31 | "module": "CommonJS", 32 | /* Specify what module code is generated. */ 33 | "rootDir": ".", 34 | /* Specify the root folder within your source files. */ 35 | "moduleResolution": "node", 36 | /* Specify how TypeScript looks up a file from a given module specifier. */ 37 | "baseUrl": "./", 38 | "paths": { 39 | "vwbl-core": ["./packages/core/src/*"], 40 | "vwbl-sdk": ["./packages/evm/src/*"], 41 | "vwbl-sdk-xrpl": ["./packages/xrpl/src/*"] 42 | }, 43 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 44 | //"typeRoots": [] /* Specify multiple folders that act like `./node_modules/@types`. */, 45 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 46 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 47 | "resolveJsonModule": true, 48 | /* Enable importing .json files */ 49 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 50 | 51 | /* JavaScript Support */ 52 | "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, 53 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 54 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 55 | 56 | /* Emit */ 57 | "declaration": true, 58 | /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 59 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 60 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 61 | "sourceMap": true, 62 | /* Create source map files for emitted JavaScript files. */ 63 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 64 | //"outDir": "./dist", 65 | /* Specify an output folder for all emitted files. */ 66 | // "removeComments": true, /* Disable emitting comments. */ 67 | // "noEmit": true, /* Disable emitting files from a compilation. */ 68 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 69 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 70 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 71 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 72 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 73 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 74 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 75 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 76 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 77 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 78 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 79 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 80 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 81 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 82 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 83 | 84 | /* Interop Constraints */ 85 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 86 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 87 | "esModuleInterop": true, 88 | /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 89 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 90 | "forceConsistentCasingInFileNames": true, 91 | /* Ensure that casing is correct in imports. */ 92 | 93 | /* Type Checking */ 94 | "strict": true, 95 | /* Enable all strict type-checking options. */ 96 | "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied `any` type.. */, 97 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 98 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 99 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 100 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 101 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 102 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 103 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 104 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 105 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 106 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 107 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 108 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 109 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 110 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 111 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 112 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 113 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 114 | 115 | /* Completeness */ 116 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 117 | "skipLibCheck": true 118 | /* Skip type checking all .d.ts files. */ 119 | }, 120 | "exclude": ["node_modules"] 121 | } -------------------------------------------------------------------------------- /packages/evm/src/vwbl/blockchain/erc721/VWBLProtocol.ts: -------------------------------------------------------------------------------- 1 | import { Web3 } from "web3"; 2 | 3 | import vwbl from "../../../contract/VWBLERC721ERC2981.json"; 4 | import vwblIPFS from "../../../contract/VWBLERC721ERC2981ForMetadata.json"; 5 | import { getFeeSettingsBasedOnEnvironment } from "../../../util/transactionHelper"; 6 | import { GasSettings, GrantViewPermissionTxParam, MintForIPFSTxParam, MintTxParam } from "../../types"; 7 | 8 | export class VWBLNFT { 9 | private contract: any; // eslint-disable-line @typescript-eslint/no-explicit-any 10 | protected web3: Web3; 11 | 12 | constructor(web3: Web3, address: string, isIpfs: boolean) { 13 | this.web3 = web3; 14 | this.contract = isIpfs ? new web3.eth.Contract(vwblIPFS.abi, address) : new web3.eth.Contract(vwbl.abi, address); 15 | } 16 | 17 | async mintToken(mintParam: MintTxParam) { 18 | const myAddress = (await this.web3.eth.getAccounts())[0]; 19 | const fee = await this.getFee(); 20 | let txSettings: unknown; 21 | if (mintParam.gasSettings?.gasPrice) { 22 | const gas = await this.contract.methods 23 | .mint(mintParam.decryptUrl, mintParam.feeNumerator, mintParam.documentId) 24 | .estimateGas({ from: myAddress, value: fee }); 25 | txSettings = { 26 | from: myAddress, 27 | value: fee, 28 | gasPrice: mintParam.gasSettings?.gasPrice, 29 | gas, 30 | }; 31 | } else { 32 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 33 | getFeeSettingsBasedOnEnvironment( 34 | mintParam.gasSettings?.maxPriorityFeePerGas, 35 | mintParam.gasSettings?.maxFeePerGas 36 | ); 37 | txSettings = { 38 | from: myAddress, 39 | value: fee, 40 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 41 | maxFeePerGas: _maxFeePerGas, 42 | }; 43 | } 44 | console.log("transaction start"); 45 | const receipt = await this.contract.methods 46 | .mint(mintParam.decryptUrl, mintParam.feeNumerator, mintParam.documentId) 47 | .send(txSettings); 48 | console.log("transaction end"); 49 | const tokenId: number = receipt.events.Transfer.returnValues.tokenId; 50 | return tokenId; 51 | } 52 | 53 | async mintTokenForIPFS(mintForIPFSParam: MintForIPFSTxParam) { 54 | const myAddress = (await this.web3.eth.getAccounts())[0]; 55 | const fee = await this.getFee(); 56 | let txSettings: unknown; 57 | if (mintForIPFSParam.gasSettings?.gasPrice) { 58 | const gas = await this.contract.methods 59 | .mint( 60 | mintForIPFSParam.metadataUrl, 61 | mintForIPFSParam.decryptUrl, 62 | mintForIPFSParam.feeNumerator, 63 | mintForIPFSParam.documentId 64 | ) 65 | .estimateGas({ from: myAddress, value: fee }); 66 | txSettings = { 67 | from: myAddress, 68 | value: fee, 69 | gasPrice: mintForIPFSParam.gasSettings?.gasPrice, 70 | gas, 71 | }; 72 | } else { 73 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 74 | getFeeSettingsBasedOnEnvironment( 75 | mintForIPFSParam.gasSettings?.maxPriorityFeePerGas, 76 | mintForIPFSParam.gasSettings?.maxFeePerGas 77 | ); 78 | txSettings = { 79 | from: myAddress, 80 | value: fee, 81 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 82 | maxFeePerGas: _maxFeePerGas, 83 | }; 84 | } 85 | console.log("transaction start"); 86 | const receipt = await this.contract.methods 87 | .mint( 88 | mintForIPFSParam.metadataUrl, 89 | mintForIPFSParam.decryptUrl, 90 | mintForIPFSParam.feeNumerator, 91 | mintForIPFSParam.documentId 92 | ) 93 | .send(txSettings); 94 | console.log("transaction end"); 95 | const tokenId: number = receipt.events.Transfer.returnValues.tokenId; 96 | return tokenId; 97 | } 98 | 99 | async getOwnTokenIds() { 100 | const myAddress = (await this.web3.eth.getAccounts())[0]; 101 | const balance = await this.contract.methods.balanceOf(myAddress).call(); 102 | return await Promise.all( 103 | range(Number.parseInt(balance)).map(async (i) => { 104 | const ownTokenId = await this.contract.methods.tokenOfOwnerByIndex(myAddress, i).call(); 105 | return Number.parseInt(ownTokenId); 106 | }) 107 | ); 108 | } 109 | 110 | async getTokenByMinter(address: string) { 111 | return await this.contract.methods.getTokenByMinter(address).call(); 112 | } 113 | 114 | async getMetadataUrl(tokenId: number) { 115 | return await this.contract.methods.tokenURI(tokenId).call(); 116 | } 117 | 118 | async getOwner(tokenId: number) { 119 | return await this.contract.methods.ownerOf(tokenId).call(); 120 | } 121 | 122 | async getMinter(tokenId: number) { 123 | return await this.contract.methods.getMinter(tokenId).call(); 124 | } 125 | 126 | async checkViewPermission(tokenId: number, user: string) { 127 | return await this.contract.methods.checkViewPermission(tokenId, user).call(); 128 | } 129 | 130 | async isOwnerOf(tokenId: number) { 131 | const myAddress = (await this.web3.eth.getAccounts())[0]; 132 | const owner = await this.getOwner(tokenId); 133 | return myAddress === owner; 134 | } 135 | 136 | async isMinterOf(tokenId: number) { 137 | const myAddress = (await this.web3.eth.getAccounts())[0]; 138 | const minter = await this.getMinter(tokenId); 139 | return myAddress === minter; 140 | } 141 | 142 | async isGranteeOf(tokenId: number) { 143 | const myAddress = (await this.web3.eth.getAccounts())[0]; 144 | return await this.checkViewPermission(tokenId, myAddress); 145 | } 146 | 147 | async getFee() { 148 | return await this.contract.methods.getFee().call(); 149 | } 150 | 151 | async getTokenInfo(tokenId: number) { 152 | return await this.contract.methods.tokenIdToTokenInfo(tokenId).call(); 153 | } 154 | 155 | async approve(operator: string, tokenId: number, gasSettings?: GasSettings): Promise { 156 | const myAddress = (await this.web3.eth.getAccounts())[0]; 157 | let txSettings: unknown; 158 | if (gasSettings?.gasPrice) { 159 | const gas = await this.contract.methods.approve(operator, tokenId).estimateGas({ from: myAddress }); 160 | txSettings = { 161 | from: myAddress, 162 | gasPrice: gasSettings?.gasPrice, 163 | gas, 164 | }; 165 | } else { 166 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 167 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 168 | txSettings = { 169 | from: myAddress, 170 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 171 | maxFeePerGas: _maxFeePerGas, 172 | }; 173 | } 174 | await this.contract.methods.approve(operator, tokenId).send(txSettings); 175 | } 176 | 177 | async getApproved(tokenId: number): Promise { 178 | return await this.contract.methods.getApproved(tokenId).call(); 179 | } 180 | 181 | async setApprovalForAll(operator: string, gasSettings?: GasSettings): Promise { 182 | const myAddress = (await this.web3.eth.getAccounts())[0]; 183 | let txSettings: unknown; 184 | if (gasSettings?.gasPrice) { 185 | const gas = await this.contract.methods.setApprovalForAll(operator, true).estimateGas({ from: myAddress }); 186 | txSettings = { 187 | from: myAddress, 188 | gasPrice: gasSettings?.gasPrice, 189 | gas, 190 | }; 191 | } else { 192 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 193 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 194 | txSettings = { 195 | from: myAddress, 196 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 197 | maxFeePerGas: _maxFeePerGas, 198 | }; 199 | } 200 | await this.contract.methods.setApprovalForAll(operator, true).send(txSettings); 201 | } 202 | 203 | async isApprovedForAll(owner: string, operator: string): Promise { 204 | return await this.contract.methods.isApprovedForAll(owner, operator).call(); 205 | } 206 | 207 | async safeTransfer(to: string, tokenId: number, gasSettings?: GasSettings): Promise { 208 | const myAddress = (await this.web3.eth.getAccounts())[0]; 209 | let txSettings: unknown; 210 | if (gasSettings?.gasPrice) { 211 | const gas = await this.contract.methods.safeTransferFrom(myAddress, to, tokenId).estimateGas({ from: myAddress }); 212 | txSettings = { 213 | from: myAddress, 214 | gasPrice: gasSettings?.gasPrice, 215 | gas, 216 | }; 217 | } else { 218 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 219 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 220 | txSettings = { 221 | from: myAddress, 222 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 223 | maxFeePerGas: _maxFeePerGas, 224 | }; 225 | } 226 | await this.contract.methods.safeTransferFrom(myAddress, to, tokenId).send(txSettings); 227 | } 228 | 229 | async grantViewPermission(grantParam: GrantViewPermissionTxParam): Promise { 230 | const myAddress = (await this.web3.eth.getAccounts())[0]; 231 | let txSettings: unknown; 232 | if (grantParam.gasSettings?.gasPrice) { 233 | const gas = await this.contract.methods 234 | .grantViewPermission(grantParam.tokenId, grantParam.grantee) 235 | .estimateGas({ from: myAddress }); 236 | txSettings = { 237 | from: myAddress, 238 | gasPrice: grantParam.gasSettings?.gasPrice, 239 | gas, 240 | }; 241 | } else { 242 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 243 | getFeeSettingsBasedOnEnvironment( 244 | grantParam.gasSettings?.maxPriorityFeePerGas, 245 | grantParam.gasSettings?.maxFeePerGas 246 | ); 247 | txSettings = { 248 | from: myAddress, 249 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 250 | maxFeePerGas: _maxFeePerGas, 251 | }; 252 | } 253 | await this.contract.methods.grantViewPermission(grantParam.tokenId, grantParam.grantee).send(txSettings); 254 | } 255 | 256 | async revokeViewPermission(tokenId: number, revoker: string, gasSettings?: GasSettings): Promise { 257 | const myAddress = (await this.web3.eth.getAccounts())[0]; 258 | let txSettings: unknown; 259 | if (gasSettings?.gasPrice) { 260 | const gas = await this.contract.methods.revokeViewPermission(tokenId, revoker).estimateGas({ from: myAddress }); 261 | txSettings = { 262 | from: myAddress, 263 | gasPrice: gasSettings?.gasPrice, 264 | gas, 265 | }; 266 | } else { 267 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 268 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 269 | txSettings = { 270 | from: myAddress, 271 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 272 | maxFeePerGas: _maxFeePerGas, 273 | }; 274 | } 275 | await this.contract.methods.revokeViewPermission(tokenId, revoker).send(txSettings); 276 | } 277 | } 278 | 279 | const range = (length: number) => { 280 | return Array.from(Array(length).keys()); 281 | }; 282 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /packages/evm/src/vwbl/blockchain/erc1155/VWBLProtocolEthers.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | import vwbl1155 from "../../../contract/VWBLERC1155ERC2981.json"; 4 | import vwbl1155IPFS from "../../../contract/VWBLERC1155ERC2981ForMetadata.json"; 5 | import { getFeeSettingsBasedOnEnvironment } from "../../../util/transactionHelper"; 6 | import { GasSettings } from "../../types"; 7 | 8 | export class VWBLERC1155EthersContract { 9 | private ethersProvider: ethers.providers.BaseProvider; 10 | private ethersSigner: ethers.Signer; 11 | private contract: ethers.Contract; 12 | 13 | constructor( 14 | address: string, 15 | isIpfs: boolean, 16 | ethersProvider: ethers.providers.BaseProvider, 17 | ethersSigner: ethers.Signer 18 | ) { 19 | this.ethersProvider = ethersProvider; 20 | this.ethersSigner = ethersSigner; 21 | this.contract = isIpfs 22 | ? new ethers.Contract(address, vwbl1155IPFS.abi, ethersSigner) 23 | : new ethers.Contract(address, vwbl1155.abi, ethersSigner); 24 | } 25 | 26 | async mintToken( 27 | decryptUrl: string, 28 | amount: number, 29 | feeNumerator: number, 30 | documentId: string, 31 | gasSettings?: GasSettings 32 | ) { 33 | const fee = await this.getFee(); 34 | let txSettings: unknown; 35 | if (gasSettings?.gasPrice) { 36 | txSettings = { 37 | value: fee, 38 | gasPrice: gasSettings?.gasPrice, 39 | }; 40 | } else { 41 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 42 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 43 | txSettings = { 44 | value: fee, 45 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 46 | maxFeePerGas: _maxFeePerGas, 47 | }; 48 | } 49 | console.log("transaction start"); 50 | const tx = await this.contract.mint(decryptUrl, amount, feeNumerator, documentId, txSettings); 51 | const receipt = await this.ethersProvider.waitForTransaction(tx.hash); 52 | console.log("transaction end"); 53 | const tokenId = parseToTokenId(receipt); 54 | return tokenId; 55 | } 56 | 57 | async batchMintToken( 58 | decryptUrl: string, 59 | amount: number[], 60 | feeNumerator: number[], 61 | documentId: string[], 62 | gasSettings?: GasSettings 63 | ) { 64 | const fee = await this.getFee(); 65 | let txSettings: unknown; 66 | if (gasSettings?.gasPrice) { 67 | txSettings = { 68 | value: fee, 69 | gasPrice: gasSettings?.gasPrice, 70 | }; 71 | } else { 72 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 73 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 74 | txSettings = { 75 | value: fee, 76 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 77 | maxFeePerGas: _maxFeePerGas, 78 | }; 79 | } 80 | console.log("transaction start"); 81 | const tx = await this.contract.mintBatch(decryptUrl, amount, feeNumerator, documentId, txSettings); 82 | const receipt = await this.ethersProvider.waitForTransaction(tx.hash); 83 | console.log("transaction end"); 84 | const tokenIds = parseToTokenIds(receipt); 85 | return tokenIds; 86 | } 87 | 88 | async mintTokenForIPFS( 89 | metadataUrl: string, 90 | decryptUrl: string, 91 | amount: number, 92 | feeNumerator: number, 93 | documentId: string, 94 | gasSettings?: GasSettings 95 | ) { 96 | const fee = await this.getFee(); 97 | let txSettings: unknown; 98 | if (gasSettings?.gasPrice) { 99 | txSettings = { 100 | value: fee, 101 | gasPrice: gasSettings?.gasPrice, 102 | }; 103 | } else { 104 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 105 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 106 | txSettings = { 107 | value: fee, 108 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 109 | maxFeePerGas: _maxFeePerGas, 110 | }; 111 | } 112 | console.log("transaction start"); 113 | const tx = await this.contract.mint(metadataUrl, decryptUrl, amount, feeNumerator, documentId, txSettings); 114 | const receipt = await this.ethersProvider.waitForTransaction(tx.hash); 115 | console.log("transaction end"); 116 | const tokenId = parseToTokenId(receipt); 117 | return tokenId; 118 | } 119 | 120 | async batchMintTokenForIPFS( 121 | metadataUrl: string, 122 | decryptUrl: string, 123 | amount: number[], 124 | feeNumerator: number[], 125 | documentId: string[], 126 | gasSettings?: GasSettings 127 | ) { 128 | const fee = await this.getFee(); 129 | let txSettings: unknown; 130 | if (gasSettings?.gasPrice) { 131 | txSettings = { 132 | value: fee, 133 | gasPrice: gasSettings?.gasPrice, 134 | }; 135 | } else { 136 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 137 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 138 | txSettings = { 139 | value: fee, 140 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 141 | maxFeePerGas: _maxFeePerGas, 142 | }; 143 | } 144 | console.log("transaction start"); 145 | const tx = await this.contract.mintBatch(metadataUrl, decryptUrl, amount, feeNumerator, documentId, txSettings); 146 | const receipt = await this.ethersProvider.waitForTransaction(tx.hash); 147 | console.log("transaction end"); 148 | const tokenIds = parseToTokenIds(receipt); 149 | return tokenIds; 150 | } 151 | 152 | async getOwnTokenIds() { 153 | const myAddress = await this.ethersSigner.getAddress(); 154 | const balance = await this.contract.callStatic.tokenCountOfOwner(myAddress); 155 | return await Promise.all( 156 | range(Number.parseInt(balance)).map(async (i) => { 157 | const ownTokenId = await this.contract.callStatic.tokenOfOwnerByIndex(myAddress, i); 158 | return Number.parseInt(ownTokenId); 159 | }) 160 | ); 161 | } 162 | 163 | async getTokenByMinter(address: string) { 164 | return await this.contract.callStatic.getTokenByMinter(address); 165 | } 166 | 167 | async getMetadataUrl(tokenId: number) { 168 | return await this.contract.callStatic.uri(tokenId); 169 | } 170 | 171 | async getOwner(tokenId: number) { 172 | return await this.contract.callStatic.ownerOf(tokenId); 173 | } 174 | 175 | async getMinter(tokenId: number) { 176 | return await this.contract.callStatic.getMinter(tokenId); 177 | } 178 | 179 | async isOwnerOf(tokenId: number) { 180 | const myAddress = await this.ethersSigner.getAddress(); 181 | const owner = await this.getOwner(tokenId); 182 | return myAddress === owner; 183 | } 184 | 185 | async isMinterOf(tokenId: number) { 186 | const myAddress = await this.ethersSigner.getAddress(); 187 | const minter = await this.getMinter(tokenId); 188 | return myAddress === minter; 189 | } 190 | 191 | async getFee() { 192 | return await this.contract.callStatic.getFee(); 193 | } 194 | 195 | async getTokenInfo(tokenId: number) { 196 | return await this.contract.callStatic.tokenIdToTokenInfo(tokenId); 197 | } 198 | 199 | async setApprovalForAll(operator: string, gasSettings?: GasSettings): Promise { 200 | let txSettings: unknown; 201 | if (gasSettings?.gasPrice) { 202 | txSettings = { 203 | gasPrice: gasSettings?.gasPrice, 204 | }; 205 | } else { 206 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 207 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 208 | txSettings = { 209 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 210 | maxFeePerGas: _maxFeePerGas, 211 | }; 212 | } 213 | const tx = await this.contract.setApprovalForAll(operator, true, txSettings); 214 | await this.ethersProvider.waitForTransaction(tx.hash); 215 | } 216 | 217 | async isApprovedForAll(owner: string, operator: string): Promise { 218 | return await this.contract.callStatic.isApprovedForAll(owner, operator); 219 | } 220 | 221 | async safeTransfer( 222 | to: string, 223 | tokenId: number, 224 | amount: number, 225 | data: string, 226 | gasSettings?: GasSettings 227 | ): Promise { 228 | const myAddress = await this.ethersSigner.getAddress(); 229 | let txSettings: unknown; 230 | if (gasSettings?.gasPrice) { 231 | txSettings = { 232 | gasPrice: gasSettings?.gasPrice, 233 | }; 234 | } else { 235 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 236 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 237 | txSettings = { 238 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 239 | maxFeePerGas: _maxFeePerGas, 240 | }; 241 | } 242 | const tx = await this.contract.safeTransferFrom(myAddress, to, tokenId, amount, data, txSettings); 243 | await this.ethersProvider.waitForTransaction(tx.hash); 244 | } 245 | 246 | async balanceOf(owner: string, tokenId: number) { 247 | return await this.contract.callStatic.balanceOf(owner, tokenId); 248 | } 249 | 250 | async balanceOfBatch(owners: string[], tokenIds: number[]) { 251 | return await this.contract.callStatic.balanceOfBatch(owners, tokenIds); 252 | } 253 | 254 | async burn(owner: string, tokenId: number, amount: number, gasSettings?: GasSettings) { 255 | let txSettings: unknown; 256 | if (gasSettings?.gasPrice) { 257 | txSettings = { 258 | gasPrice: gasSettings?.gasPrice, 259 | }; 260 | } else { 261 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 262 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 263 | txSettings = { 264 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 265 | maxFeePerGas: _maxFeePerGas, 266 | }; 267 | } 268 | const tx = await this.contract.burn(owner, tokenId, amount, txSettings); 269 | await this.ethersProvider.waitForTransaction(tx.hash); 270 | } 271 | 272 | async burnBatch(owner: string, tokenIds: number[], amount: number[], gasSettings?: GasSettings) { 273 | let txSettings: unknown; 274 | if (gasSettings?.gasPrice) { 275 | txSettings = { 276 | gasPrice: gasSettings?.gasPrice, 277 | }; 278 | } else { 279 | const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } = 280 | getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas); 281 | txSettings = { 282 | maxPriorityFeePerGas: _maxPriorityFeePerGas, 283 | maxFeePerGas: _maxFeePerGas, 284 | }; 285 | } 286 | const tx = await this.contract.burnBatch(owner, tokenIds, amount, txSettings); 287 | await this.ethersProvider.waitForTransaction(tx.hash); 288 | } 289 | } 290 | 291 | const range = (length: number) => { 292 | return Array.from(Array(length).keys()); 293 | }; 294 | 295 | const parseToTokenId = (receipt: ethers.providers.TransactionReceipt): number => { 296 | const eventInterface = new ethers.utils.Interface([ 297 | "event erc1155DataRegistered(address contractAddress, uint256 tokenId)", 298 | ]); 299 | let tokenId = 0; 300 | receipt.logs.forEach((log) => { 301 | // check whether topic is erc1155DataRegistered(address contractAddress, uint256 tokenId) 302 | if (log.topics[0] === "0xf30a336bd6229f1e88c41eeaad2c5fa73b69e4ec90773a67af474031d64fe32f") { 303 | const description = eventInterface.parseLog({ topics: log.topics, data: log.data }); 304 | tokenId = description.args[1].toNumber(); 305 | } 306 | }); 307 | return tokenId; 308 | }; 309 | 310 | const parseToTokenIds = (receipt: ethers.providers.TransactionReceipt): number[] => { 311 | const eventInterface = new ethers.utils.Interface([ 312 | "event erc1155DataRegistered(address contractAddress, uint256 tokenId)", 313 | ]); 314 | const tokenIds: number[] = []; 315 | receipt.logs.forEach((log) => { 316 | // check whether topic is erc1155DataRegistered(address contractAddress, uint256 tokenId) 317 | if (log.topics[0] === "0xf30a336bd6229f1e88c41eeaad2c5fa73b69e4ec90773a67af474031d64fe32f") { 318 | const description = eventInterface.parseLog({ topics: log.topics, data: log.data }); 319 | tokenIds.push(description.args[1].toNumber()); 320 | } 321 | }); 322 | return tokenIds; 323 | }; 324 | --------------------------------------------------------------------------------