Tapestry Social Graph Integration
You are building social features for a React Native (Expo) mobile app called Copium - a social trading app using Tapestry and Polymarket.
Project Context
- Framework: React Native with Expo Router (file-based routing)
- Auth: Privy (OAuth + embedded Solana wallet) + Mobile Wallet Adapter (MWA)
- Wallet hook:
useUnifiedWallet() from @/hooks/use-wallet provides address, walletType, provider, isConnected, logout
- State: React Query (
@tanstack/react-query) for server state
- Styling: StyleSheet with
#0a0a0a bg, #232323 borders, PlusJakartaSans font
- API Key:
process.env.TAPESTRY_API_KEY (server-side only, stored in .env.local)
- Base URL:
https://api.usetapestry.dev/api/v1
API Authentication
All endpoints require apiKey as a query parameter:
GET /profiles/{id}?apiKey=YOUR_API_KEY
The API key is in .env.local as TAPESTRY_API_KEY. Since this is a React Native app (no server-side routes), call the Tapestry API directly from a utility module, keeping the API key secure via process.env.TAPESTRY_API_KEY (bundled at build time but not exposed to web).
Execution Methods (for write operations)
| Method | Speed | Use When |
|---|
FAST_UNCONFIRMED | <1s | Default. Returns after graph DB write, on-chain tx in background |
QUICK_SIGNATURE | ~5s | Need tx signature but don't need confirmation |
CONFIRMED_AND_PARSED | ~15s | Need full on-chain confirmation |
API Client Pattern
Create API calls in lib/tapestry/ directory. Use this pattern:
typescript
1// lib/tapestry/client.ts
2const TAPESTRY_BASE = 'https://api.usetapestry.dev/api/v1';
3const API_KEY = process.env.TAPESTRY_API_KEY!;
4
5export async function tapestryFetch<T>(
6 endpoint: string,
7 options?: { method?: string; body?: any; params?: Record<string, string> }
8): Promise<T> {
9 const { method = 'GET', body, params = {} } = options ?? {};
10 const url = new URL(`${TAPESTRY_BASE}${endpoint}`);
11 url.searchParams.set('apiKey', API_KEY);
12 Object.entries(params).forEach(([k, v]) => v && url.searchParams.set(k, v));
13
14 const res = await fetch(url.toString(), {
15 method,
16 headers: body ? { 'Content-Type': 'application/json' } : undefined,
17 body: body ? JSON.stringify(body) : undefined,
18 });
19
20 if (!res.ok) {
21 const err = await res.json().catch(() => ({}));
22 throw new Error(err.error || `Tapestry API error: ${res.status}`);
23 }
24 return res.json();
25}
React Hook Pattern
typescript
1// hooks/use-tapestry-profile.ts
2import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
3import { tapestryFetch } from '@/lib/tapestry/client';
4
5export function useTapestryProfile(username: string) {
6 return useQuery({
7 queryKey: ['tapestry-profile', username],
8 queryFn: () => tapestryFetch<ProfileResponse>(`/profiles/${username}`),
9 enabled: !!username,
10 });
11}
All Endpoints Reference
For detailed endpoint specs, see reference.md.
Quick Endpoint Map
Profiles
GET /profiles/ - List/search profiles (by wallet, phone, twitter, email)
POST /profiles/findOrCreate - Find or create profile
GET /profiles/{id} - Get profile by ID or username
PUT /profiles/{id} - Update profile
GET /profiles/{id}/followers - Get followers
GET /profiles/{id}/following - Get following
GET /profiles/{id}/followers/global - Cross-namespace followers
GET /profiles/{id}/following/global - Cross-namespace following
GET /profiles/{id}/following-who-follow - Mutual connections
GET /profiles/{id}/wallets - Get linked wallets
PATCH /profiles/{id}/wallets - Link wallets
DELETE /profiles/{id}/wallets - Unlink wallets
PATCH /profiles/{id}/contacts - Link contacts
DELETE /profiles/{id}/contacts - Unlink contacts
POST /profiles/{id}/notification - Send notification
GET /profiles/{id}/referrals - Get referral tree
GET /profiles/{id}/suggested-profiles - Suggested follows
GET /profiles/suggested/{identifier} - Suggestions by wallet/contact
GET /profiles/suggested/{identifier}/global - Cross-namespace suggestions
GET /profiles/token-owners/{tokenAddress} - Token holder profiles
GET /search/profiles - Search profiles by text
Social Graph (Followers)
POST /followers/add - Follow { startId, endId }
POST /followers/remove - Unfollow { startId, endId }
GET /followers/state - Check follow state ?startId=&endId=
Content (Posts)
GET /contents/ - List content (with filters, ordering, pagination)
GET /contents/aggregation - Aggregate content properties
POST /contents/batch/read - Batch get by IDs (max 20)
POST /contents/findOrCreate - Create content { id, profileId, properties: [{key,value}] }
GET /contents/{id} - Get content details
PUT /contents/{id} - Update content properties
DELETE /contents/{id} - Delete content
Comments
GET /comments/ - List comments ?contentId=&profileId=&targetProfileId=
POST /comments/ - Create comment { profileId, text, contentId?, commentId?, targetProfileId? }
POST /comments/batch/read - Batch get (max 20)
GET /comments/{id} - Get comment with replies
PUT /comments/{id} - Update comment
DELETE /comments/{id} - Delete comment
GET /comments/{id}/replies - Get replies
Likes
GET /likes/{nodeId} - Get profiles who liked a node
POST /likes/{nodeId} - Like { startId }
DELETE /likes/{nodeId} - Unlike { startId }
Activity Feeds
GET /activity/feed - User activity feed ?username=
GET /activity/global - Global activity feed
GET /activity/swap - Swap activity from followed wallets ?username=&tokenAddress=
Identities
GET /identities/{id} - Resolve wallets/contacts from ID
GET /identities/{id}/profiles - Find profiles across namespaces
Trades
POST /trades/ - Log a trade
GET /trades/all-trades - Get all trades in time period
GET /trades/fetch-transaction-history - Wallet tx history
Wallets
POST /wallets/{address}/connect - Connect two wallets
GET /wallets/{address}/socialCounts - Social counts by wallet
Key Integration Points for Copium
1. Profile Creation (on signup/login)
After wallet connection + X login, call findOrCreate:
typescript
1await tapestryFetch('/profiles/findOrCreate', {
2 method: 'POST',
3 body: {
4 username: twitterHandle, // from Privy OAuth
5 walletAddress: address, // from useUnifiedWallet
6 blockchain: 'SOLANA',
7 image: twitterProfileImage,
8 bio: twitterBio,
9 contact: { id: twitterHandle, type: 'TWITTER' },
10 },
11});
2. Follow/Unfollow
typescript
1await tapestryFetch('/followers/add', {
2 method: 'POST',
3 body: { startId: myProfileId, endId: targetProfileId },
4});
3. Create a Post (Content)
typescript
1await tapestryFetch('/contents/findOrCreate', {
2 method: 'POST',
3 body: {
4 id: `post-${Date.now()}`,
5 profileId: myProfileId,
6 properties: [
7 { key: 'text', value: 'My post content' },
8 { key: 'type', value: 'trade_call' },
9 ],
10 },
11});
4. Like Content
typescript
1await tapestryFetch(`/likes/${contentId}`, {
2 method: 'POST',
3 body: { startId: myProfileId },
4});
typescript
1await tapestryFetch('/comments/', {
2 method: 'POST',
3 body: {
4 profileId: myProfileId,
5 contentId: contentId,
6 text: 'Great trade!',
7 },
8});
6. Activity Feed
typescript
1const feed = await tapestryFetch('/activity/feed', {
2 params: { username: myUsername },
3});
7. Search Users
typescript
1const results = await tapestryFetch('/search/profiles', {
2 params: { query: searchText },
3});
8. Polymarket Integration Pattern
Store Polymarket-related data as content properties:
typescript
1await tapestryFetch('/contents/findOrCreate', {
2 method: 'POST',
3 body: {
4 id: `prediction-${marketId}-${profileId}`,
5 profileId: myProfileId,
6 properties: [
7 { key: 'type', value: 'prediction' },
8 { key: 'marketId', value: polymarketConditionId },
9 { key: 'position', value: 'YES' },
10 { key: 'amount', value: 50 },
11 { key: 'odds', value: 0.65 },
12 ],
13 },
14});
Response Types
typescript
1interface TapestryProfile {
2 id: string;
3 namespace: string;
4 created_at: number;
5 username: string;
6 bio: string | null;
7 image: string | null;
8}
9
10interface ProfileResponse {
11 profile: TapestryProfile;
12 walletAddress: string;
13 socialCounts: { followers: number; following: number };
14 namespace: { name: string | null; readableName: string | null };
15}
16
17interface ContentResponse {
18 id: string;
19 created_at: number;
20 properties: Record<string, any>;
21}
22
23interface CommentResponse {
24 comment: { id: string; created_at: number; text: string };
25 author: TapestryProfile;
26 socialCounts: { likeCount: number };
27 requestingProfileSocialInfo?: { hasLiked: boolean };
28}
29
30interface ActivityItem {
31 type: 'following' | 'new_content' | 'like' | 'comment' | 'new_follower';
32 actor_id: string;
33 actor_username: string;
34 target_id?: string;
35 target_username?: string;
36 timestamp: number;
37 activity: string;
38}
39
40interface PaginatedResponse<T> {
41 page: number;
42 pageSize: number;
43 totalCount: number;
44 data?: T[];
45 profiles?: T[];
46 activities?: T[];
47}
File Structure Convention
lib/tapestry/
client.ts # Base fetch wrapper
profiles.ts # Profile API functions
social.ts # Follow/unfollow/like functions
content.ts # Content/posts CRUD
comments.ts # Comments CRUD
activity.ts # Activity feed functions
types.ts # TypeScript interfaces
hooks/
use-tapestry-profile.ts
use-tapestry-feed.ts
use-tapestry-social.ts
use-tapestry-content.ts
When implementing features with $ARGUMENTS, refer to reference.md for full endpoint details.