JS SDK
安装
npm i osp-client-js@latest --save-dev
Auth
登录--signIn
signIn 用户在获取用户钱包地址后,登录 DAPP
const res = await client.auth.signIn();
案例
import React from "react";
import { Client } from "osp-client-js";
export function Auth({ client }: { client: Client }) {
const handleSignIn = async () => {
const res = await client.auth.signIn();
console.log("res", res);
};
return (
<>
<h2>Auth模块</h2>
<h3>登录</h3>
<button onClick={handleSignIn}>signIn</button>
</>
);
}
参数
无
返回
{
err: Error,
data: {
accessToken: string,
refreshToken: string
}
}
Porfile
创建用户的 Profile
账户的所有操作都是基于创建的 profile,所以在进行其他操作前,需要先创建 profile
```typescript
const res = await client.user.create(...)
案例
import React from "react";
import { Client } from "osp-client-js";
// 一个需要付费关注的 mock 数据
const mock = {
// 用户关注时候是何种方式关注
follow_module: {
/***
* 付费关注(当type不为付费关注时候init为null)
* type: FollowModuleEnum
* 免费关注
* FeeFollowModule
* 用户必须有profile才能关注
* ProfileFollowModule
* NULL
* NullFollowModule
*
*/
type: "FeeFollowModule",
init: {
// 付费关注
FeeFollowModule: {
amount: {
// 代币地址(用那种代币支付)
currency: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
// 数量
value: "1",
},
// 接收账户
recipient: "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65",
},
},
},
// 用户名(必须为字母与数字的组合)
handle: "aaaa11",
// 简介
bio: "1",
// 头像
avatar: "1",
// 封面
cover_picture: "1",
};
export function Create({ client }: { client: Client }) {
const createProfiles = async () => {
const {
follow_module,
follow_nft_currency,
follow_nft_amount,
follow_nft_recived,
...rest
} = form;
const res = await client.user.create(mock);
console.log("res", res);
};
return (
<>
<p>注意:头像为必传, 用户名不能少于5位,创建profile需要先登陆</p>
<button onClick={createProfiles}>创建用户的profile</button>
</>
);
}
返回
{
err: Error,
data: {
obj: {
/** txHash if success */
tx_hash?: string;
/** txId if indexed by relay */
tx_id?: string;
info?: object;
}
}
}
获取用户所有的的 Profile
import React from "react";
import { Client } from "osp-client-js";
client.user.getAllProfile();
返回
{
err: Error,
data: {
obj?: {
/**
* @format int32
* @min 1
* @max 20
*/
limit?: number;
/** @format int32 */
total?: number;
next_token?: string;
rows?: User[];
}
}
}
获取用户所有的的 Profile 详情
import React from "react";
import { Client } from "osp-client-js";
// profileId 为User的profile_id
const profileId = "0xf7";
client.user.get(profileId);
返回
{
err: Error,
data: {
obj?: User;
}
}
Activity
mock 数据
const metadata: any = {
version: "2.0.0",
metadata_id: "c58be35b-b036-41ff-810d-959d08685ea8",
content: "hihi",
external_url: "https://lenster.xyz/u/diwucun.test",
image: null,
imageMimeType: null,
name: "testpostdy",
tags: [],
animation_url: null,
mainContentFocus: "TEXT_ONLY",
contentWarning: null,
attributes: [
{ traitType: "type", displayType: "string", value: "text_only" },
],
media: [],
locale: "zh-CN",
appId: "Lenster",
};
const collect = {
type: CollectModuleEnum.FreeCollectModule,
init: {
FreeCollectModule: {
only_follower: false,
},
},
};
const reference = {
type: ReferenceModuleEnum.FollowerOnlyReferenceModule,
init: {},
};
创建帖子 Activity--addActivity
用户登录后,在拥有 profile 后,创建不上链的 Activity
client.activity.addActivity({
profileId,
metadata,
collect,
reference,
});
案例
mock:获取 mock 数据
import React from "react";
import { Client } from "osp-client-js";
// metadata,collect,reference 与上面案例一致
export function createActivity({ client }: { client: Client }) {
const createActivity = async () => {
await client.activity.addActivity(
{
profile_id: profileId,
content_uri,
collect_module: collect,
reference_module: reference,
},
{ headers: { "On-Chain": `${isOnChain}` } }
);
};
return (
<>
<button onClick={createActivity}>创建Acvitity</button>
</>
);
}
返回
{
err: Error,
data: {
obj:{
/** txHash if success */
tx_hash?: string;
/** txId if indexed by relay */
tx_id?: string;
info?: object;
}
}
}
创建帖子 Activity--addActivityBroadCast
用户登录后,在拥有 profile 后,通过广播的形式创建 activity
client.activity.addActivityBroadCast({
profileId,
metadata,
collect,
reference,
});
案例
mock:获取 mock 数据
import React from "react";
import { Client } from "osp-client-js";
// metadata,collect,reference 与上面案例一致
export function addActivityBroadCast({ client }: { client: Client }) {
const addActivityBroadCast = async () => {
await client.activity.addActivityBroadCast({
profileId,
metadata,
collect,
reference,
});
};
return (
<>
<button onClick={addActivityBroadCast}>创建Acvitity</button>
</>
);
}
返回
{
err: Error,
data: {
obj:{
tx_hash: string;
}
}
}
创建帖子 Activity--createActivityPostWithSig
通过调用合约的 PostWithSig 方法创建帖子 Activity 这种调用合约的方式能显示签名信息
client.activity.createActivityPostWithSig({
profileId,
metadata,
collect,
reference,
});
案例
mock:获取 mock 数据
import React from "react";
import { Client } from "osp-client-js";
// metadata,collect,reference 使用与上面例子项目的数据即可
export function createActivityPostWithSig({ client }: { client: Client }) {
const createActivityPostWithSig = async () => {
await client.activity.createActivityPostWithSig({
profile_id: profileId,
content_uri,
collect_module: collect,
reference_module: reference,
});
};
return (
<>
<button onClick={createActivityPostWithSig}>创建Acvitity</button>
</>
);
}
返回
{
err: Error | "";
}
创建帖子 Activity--createActivityPost
通过调用合约的 post 方法创建帖子 Activity 这种调用合约的方式无法不显示签名信息
client.activity.createActivityPost({
profileId,
metadata,
collect,
reference,
});
案例
mock:获取 mock 数据
import React from "react";
import { Client } from "osp-client-js";
// metadata,collect,reference 使用与上面例子项目的数据即可
export function createActivityPost({ client }: { client: Client }) {
const createActivityPost = async () => {
await client.activity.createActivityPost({
profile_id: profileId,
content_uri,
collect_module: collect,
reference_module: reference,
});
};
return (
<>
<button onClick={createActivityPost}>创建Acvitity</button>
</>
);
}
返回
{
err: Error | "";
}
Relation
关注用户 —— dispatcher
// 通过header参数On-Chain决定否上链 —— follow
await client.relation.follow(feedSlug, userId, body);
入参
- feedSlug: feed key
- userId: cur user id
- body: { isOnChain: if chain, targetUserId: target user id, followModuleParam: follow module param }
案例
const feedSlug = "user";
const userId = client.user.userId;
const targetUserId = "51323505354768384";
const body = {
isOnChain: false,
targetUserId,
followModuleParam: {
type: "FeeFollowModule",
amount: {
currency: "0xD40282e050723Ae26Aeb0F77022dB14470f4e011",
value: "0.01",
},
},
};
const { err, data }: any = await client.relation.follow(feedSlug, userId, body);
if (data?.code === 200) {
alert("关注成功");
}
关注用户 —— boardcast
await client.relation.followBroadcast(feedSlug, userId, body);
入参
- feedSlug: feed key
- userId: target user id
- body: { followModuleParam: follow module param }
案例
const handleDoFollowBroadCast = async () => {
// 关注人为NullFollowModule,重复关注不报错
// const userId = '0x11c';
// const body = {
// followModuleParam: {
// "type": "NullFollowModule"
// }
// };
const feedSlug = "user";
// 关注人 不为 NullFollowModule时,需要传profileId —— 重复关注报错,需提供新userId
const userId = "0x127";
const body = {
followModuleParam: {
type: "ProfileFollowModule",
data: {
profileId: client.user.profileId,
},
},
};
await client.relation.followBroadcast(feedSlug, userId, body);
};
底层逻辑
1、调用 typeData 接口获取签名数据 & boardcastId;
2、通过 web3 提供的 sendAsync 方法进行签名,获取 signatureId;
3、通过 boardcastId & signatureId 调用 boardcast 接口进行上链操作
关注用户 —— sdk(followWithSig & follow)
// followWithSig
await client.relation.createRelationPostWithSig({
userId,
feedSlug,
data,
});
// follow
await client.relation.createRelationPost({
userId,
feedSlug,
data,
});
入参
- feedSlug: feed key
- userId: target user id
- body: { followModuleParam: follow module param }
案例
const feedSlug = "user";
// 关注人 不为 NullFollowModule时,需要传profileId —— 重复关注报错,需提供新userId
const userId = "0x127";
const body = {
followModuleParam: {
type: "ProfileFollowModule",
data: {
profileId: client.user.profileId,
},
},
};
// 需要签名上链方式 —— followWithSig
const createRelationPostWithSig = async () => {
await client.relation.createRelationPostWithSig({
userId,
feedSlug,
data: body,
});
};
// 不需要签名上链方式 —— follow
const createRelationPost = async () => {
await client.relation.createRelationPost({
userId,
feedSlug,
data: body,
});
};
底层逻辑
1、调用 typeData 接口获取签名数据
2、通过 ethers 提供的方法 splitSignature 拆分签名使用
3、定义 contract(合约)
const hub = require("./LensHub.json");
this.contract = new this.web3.eth.Contract(hub.abi, CONTRACH_ADDRESS, {
from: accounts[0], // 默认交易发送地址
gasPrice: "20000000000", // 以 wei 为单位的默认 gas 价格,当前价格为 20 gwei
});
4、调用合约 followWithSig 或 follow 上链 —— 传递对应参数
我的关注列表
await client.relation.following(feedSlug, userId, query);
入参
- feedSlug: feed key
- userId: cur user id
- address: wallet address
- limit: limit count
- nextToken: next token
案例
const handleGetFollowingList = async () => {
const feedSlug = "user";
const userId = client.user.userId;
const query = {
address: client.address,
limit: 10,
};
const { err, data }: any = await client.relation.following(
feedSlug,
userId,
query
);
};
我的粉丝列表
await client.relation.followers(feedSlug, userId, query);
入参
- feedSlug: feed key
- userId: cur user id
- limit: limit count
- nextToken: next token
案例
const handleGetFollowerList = async () => {
const feedSlug = "user";
const userId = client.user.userId;
const query = {
limit: 10,
};
const { err, data }: any = await client.relation.followers(
feedSlug,
userId,
query
);
setFollowerList(data?.obj?.rows || []);
console.log("handleGetFollowerList info", data, err);
};
Reaction 模块
参数枚举 menu
/** reaction kind */
export enum ReactionKindEnum {
COMMENT = "COMMENT",
REPLY = "REPLY",
LIKE = "LIKE",
SHARE = "SHARE",
FAVORITE = "FAVORITE",
UPVOTE = "UPVOTE",
DONWVOTE = "DONWVOTE",
}
/** reaction request */
export interface ListReactionRequest {
/** reaction kind */
kind?: ReactionKindEnum;
/** reaction ranking type */
ranking?: ReactionRankingEnum;
/**
* @min 1
* @max 20
*/
limit?: number;
next_token?: string;
/** @example false */
with_activity?: boolean;
}
export type ReactionAddActivity = {
target_user_id: string;
target_activity_id: string;
content_uri: string;
reference_module_param: ReferenceModuleParam;
};
export type ReactionAddOptions = {
userId: string;
collect: CollectModule;
reference: ReferenceModule;
collectParam: any;
referenceParam: any;
isOnChain: boolean;
};
export interface Reaction {
/** user id */
user_id?: string;
/** activity id */
activity_id?: string;
/**
* The metadata uploaded somewhere passing in the url to reach it
* @format uri
*/
content_uri?: string;
/** reaction kind */
kind?: ReactionKindEnum;
collect_module?: CollectModule;
reference_module?: ReferenceModule;
}
mock 数据
const metadata: any = {
version: "2.0.0",
metadata_id: "c58be35b-b036-41ff-810d-959d08685ea8",
content: "hihi",
external_url: "https://lenster.xyz/u/diwucun.test",
image: null,
imageMimeType: null,
name: "replyByWhz",
tags: [],
animation_url: null,
mainContentFocus: "TEXT_ONLY",
contentWarning: null,
attributes: [
{ traitType: "type", displayType: "string", value: "text_only" },
],
media: [],
locale: "zh-CN",
appId: "Lenster",
};
const mockCollectModule = {
// 在帖子详情中获取
init: {},
type: CollectModuleEnum.FreeCollectModule,
};
const mockReferenceModule = {
// 在帖子详情中获取
type: ReferenceModuleEnum.NULL,
init: {},
};
const mockCollectModuleParam = {
// 在帖子详情中获取
init: {},
type: CollectModuleEnum.FreeCollectModule,
};
获取 reaction 列表
在对帖子做出评论,收藏等操作后,可以查询评论,收藏等列表
const { err, data }: any = await client.reaction.get(
lookupAttr,
lookupValue,
kind,
query
);
案例 -- 获取评论列表
import React, { useState } from "react";
import { Client } from "osp-client-js";
export function Auth({ client }: { client: Client }) {
const [commentsList, setCommentsList] = useState([]);
const handleGetCommentsList = async () => {
const lookupAttr = "ACTIVITY";
const lookupValue =
"QU5TV0VSOkFOU1dFUl80NDg5NzY2MTU4NTQ4OTkyMDoxNjgzMjc4MjI4MzgxMDAw";
const kind = "COMMENT";
const query = {
kind: "COMMENT",
ranking: "TIME",
limit: 10,
with_activity: false,
};
const { err, data }: any = await client.reaction.get(
lookupAttr,
lookupValue,
kind,
query
);
setCommentsList(data?.obj?.rows || []);
console.log("handleGetFollowingList info", data, err);
};
return (
<>
<h3>获取评论列表</h3>
<button onClick={handleGetCommentsList}>getCommentsList</button>
<p>
{commentsList.map((item, index) => {
return (
<div key={`${item.activity_id}:${index}`}>{item.activity_id}</div>
);
})}
</p>
</>
);
}
参数
{
lookupAttr: string,
lookupValue: string,
kind: ReactionKindEnum,
query: {
/** reaction request */
listReactionRequest: ListReactionRequest;
},
params: RequestParams = {},
}
返回
{
"code": 200,
"msg": "success",
"obj": {
"limit": number,
"total": number,
"next_token": string,
"rows": Reaction[]
}
}
收藏帖子
const { err, data }: any = await client.reaction.add(kind, body, options);
案例
mock: 获取 mock 数据
const kind = "FAVORITE";
const body = {
target_user_id: "44897661585489920",
target_activity_id:
"UVVFU1RJT046UVVFU1RJT05fMjAyMzA0MDcwNTUwMDIzNTUwMzUyOToxNjgxNzE5MDU5MDgzMDAw",
};
const options = {
userId: "44895703185260544",
collect_module: mockCollectModule,
reference_module: mockReferenceModule,
isOnChain: false,
};
const { err, data }: any = await client.reaction.add(kind, body, options);
参数
{
kind: ReactionKindEnum,
body: ReactionAddActivity,
options: ReactionAddOptions
}
返回
{
"code": 0,
"msg": string,
"obj": {
"tx_hash": string
}
}
收藏帖子 Broadcast
广播上链的形式
await client.reaction.addBroadcast(kind, body, options);
案例
mock: 获取 mock 数据
const handleDoFavoriteBroadcast = async () => {
const kind = "FAVORITE";
const body = {
target_user_id: "0x156",
target_activity_id:
"UE9TVDpQT1NUXzUxNjQwMTA4MTA1NjI5Njk2OjE2ODQ4MzQ0OTc0NjkwMDA=",
};
const profileId = client.user.profileId;
const options = {
user_id: profileId,
collect_module: mockCollectModule,
reference_module: mockReferenceModule,
collect_module_param: mockCollectModuleParam,
};
await client.reaction.addBroadcast(kind, body, options);
};
参数
{
kind: ReactionKindEnum,
body: ReactionAddActivity,
options: ReactionAddOptions
}
返回
{
"code": 200,
"msg": "success",
"obj": {
"tx_hash": string
}
}
收藏帖子 abi
通过 abi 方式直接上链,签名+确认的流程
await client.reaction.addPostWithSig(kind, body, options);
案例
mock: 获取 mock 数据
const handleDoFavoriteAbi = async () => {
const kind = "FAVORITE";
const body = {
target_user_id: "0x156",
target_activity_id:
"UE9TVDpQT1NUXzUxNjQwMTA4MTA1NjI5Njk2OjE2ODQ4MzQ0OTc0NjkwMDA=",
};
const profileId = client.user.profileId;
const options = {
user_id: profileId,
collect_module: mockCollectModule,
reference_module: mockReferenceModule,
collect_module_param: mockCollectModuleParam,
};
await client.reaction.addPostWithSig(kind, body, options);
};
参数
{
kind: ReactionKindEnum,
body: ReactionAddActivity,
options: ReactionAddOptions
}
返回
{
err: Error | "";
}
收藏帖子 --- post 直接上链
通过 post 方式直接上链, 无需签名,直接确认
await client.reaction.addPost(kind, body, options);
案例
mock: 获取 mock 数据
const handleDoFavoriteCollect = async () => {
const kind = "FAVORITE";
const body = {
target_user_id: "0x156",
target_activity_id:
"UE9TVDpQT1NUXzUxNjQwMTA4MTA1NjI5Njk2OjE2ODQ4MzQ0OTc0NjkwMDA=",
};
const profileId = client.user.profileId;
const options = {
user_id: profileId,
collect_module: mockCollectModule,
reference_module: mockReferenceModule,
collect_module_param: mockCollectModuleParam,
};
await client.reaction.addPost(kind, body, options);
};
参数
{
kind: ReactionKindEnum,
body: ReactionAddActivity,
options: ReactionAddOptions
}
返回
{
err: Error | "";
}
发表评论、回复(不上链)
用户登录,且拥有 profile 后,可对 Activity 发表评论。
// kind = 'COMMENT'
const { err, data }: any = await client.reaction.add(kind, body, options);
案例
mock: 获取 mock 数据
// 先将评论内容上传到ipfs
const content_uri = await client.ipfs.upload(metadata)
const kind = 'COMMENT'
const body = {
target_user_id: '51640108105629696',
target_activity_id: 'UE9TVDpQT1NUXzUxNjQwMTA4MTA1NjI5Njk2OjE2ODQ4OTkxNDI3OTUwMDA=' // activity 的 id
content_uri: content_uri
}
// const kind = 'REPLY'
// const body = {
// target_user_id: '51640108105629696',
// target_activity_id: 'Q09NTUVOVDpDT01NRU5UXzUxNjQwMTA4MTA1NjI5Njk2OjE2ODQ5MTI3MDI3NjIwMDA=', // comment 或 reply 的 id
// content_uri: content_uri
// }
const options = {
userId: client.user.userId,
collect_module: mockCollectModule,
reference_module: mockReferenceModule,
isOnChain: false
}
const {err, data}: any = await client.reaction.add(kind, body, options);
参数
{
kind: ReactionKindEnum,
body: ReactionAddActivity,
options: ReactionAddOptions
}
返回
{
"code": 0,
"msg": string,
"obj": {
"tx_hash": string
}
}
发评论、回复(上链)的请求参数
const getQueryParams = async () => {
const content_uri = await client.ipfs.upload(metadata)
const profileId = client.user.profileId;
const kind = 'COMMENT'
const body = {
target_user_id: '51640108105629696',
target_activity_id: 'UE9TVDpQT1NUXzUxNjQwMTA4MTA1NjI5Njk2OjE2ODQ4OTkxNDI3OTUwMDA=' // activity 的 id
content_uri: content_uri
}
// const kind = 'REPLY'
// const body = {
// target_user_id: '51640108105629696',
// target_activity_id: 'Q09NTUVOVDpDT01NRU5UXzUxNjQwMTA4MTA1NjI5Njk2OjE2ODQ5MTI3MDI3NjIwMDA=', // comment 或 reply 的 id
// content_uri: content_uri
// }
const options = {
user_id: profileId,
isOnChain: true,
collect_module: mockCollectModule,
reference_module: mockReferenceModule,
}
return {
kind,
body,
options
}
}
发评论、回复(CommentWithSig)
案例
mock: 获取 mock 数据
getQueryParams: 获取请求数据
// kind = 'COMMENT' | 'REPLY'
const { kind, body, options }: any = await getQueryParams();
client.reaction.createCommentWithSig(kind, body, options);
参数
{
kind: ReactionKindEnum,
body: ReactionAddActivity,
options: ReactionAddOptions
}
返回
{
err: Error | "";
}
发评论、回复(CommentNoSig)
案例
mock: 获取 mock 数据
getQueryParams: 获取请求数据
// kind = 'COMMENT' | 'REPLY'
const { kind, body, options }: any = await getQueryParams();
client.reaction.createCommentNoSig(kind, body, options);
参数
{
kind: ReactionKindEnum,
body: ReactionAddActivity,
options: ReactionAddOptions
}
返回
{
err: Error | "";
}
发评论、回复(Broadcast)
案例
mock: 获取 mock 数据
getQueryParams: 获取请求数据
// kind = 'COMMENT' | 'REPLY'
const { kind, body, options }: any = await getQueryParams();
client.reaction.addBroadcast(kind, body, options);
参数
{
kind: ReactionKindEnum,
body: ReactionAddActivity,
options: ReactionAddOptions
}
返回
{
"code": 200,
"msg": "success",
"obj": {
"tx_hash": string
}
}
点赞(不上链)
用户登录,且拥有 profile 后,可对 Activity、comment 点赞。
案例
mock: 获取 mock 数据
const kind = "LIKE";
const body = {
target_user_id: "44897661585489920",
target_activity_id:
"UE9TVDpQT1NUXzUxNjQwMTA4MTA1NjI5Njk2OjE2ODQ4OTg0NTI4MzQwMDA=",
};
const options = {
userId: "44895703185260544",
collect_module: mockCollectModule,
reference_module: mockReferenceModule,
isOnChain: false,
};
const { err, data }: any = await client.reaction.add(kind, body, options);
参数
{
kind: ReactionKindEnum,
body: ReactionAddActivity,
options: ReactionAddOptions
}
返回
{
"code": 0,
"msg": string,
"obj": {
"tx_hash": string
}
}
Demo
https://osp-client.vercel.app/
注意
如何获取钱包地址
建议您使用wagmi中的示例方法获取钱包地址。