.claude/skills/tapestry/SKILL.md
Social graph protocol integration for Copium app using Tapestry API. Use when building social features - profiles, follows, content/posts, comments, likes, activity feeds, identity resolution, or trades. Covers all 41 Tapestry API endpoints.
npx skillsauth add ForkingAwesome/copium tapestryInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are building social features for a React Native (Expo) mobile app called Copium - a social trading app using Tapestry and Polymarket.
useUnifiedWallet() from @/hooks/use-wallet provides address, walletType, provider, isConnected, logout@tanstack/react-query) for server state#0a0a0a bg, #232323 borders, PlusJakartaSans fontprocess.env.TAPESTRY_API_KEY (server-side only, stored in .env.local)https://api.usetapestry.dev/api/v1All 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).
| 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 |
Create API calls in lib/tapestry/ directory. Use this pattern:
// lib/tapestry/client.ts
const TAPESTRY_BASE = 'https://api.usetapestry.dev/api/v1';
const API_KEY = process.env.TAPESTRY_API_KEY!;
export async function tapestryFetch<T>(
endpoint: string,
options?: { method?: string; body?: any; params?: Record<string, string> }
): Promise<T> {
const { method = 'GET', body, params = {} } = options ?? {};
const url = new URL(`${TAPESTRY_BASE}${endpoint}`);
url.searchParams.set('apiKey', API_KEY);
Object.entries(params).forEach(([k, v]) => v && url.searchParams.set(k, v));
const res = await fetch(url.toString(), {
method,
headers: body ? { 'Content-Type': 'application/json' } : undefined,
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.error || `Tapestry API error: ${res.status}`);
}
return res.json();
}
// hooks/use-tapestry-profile.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { tapestryFetch } from '@/lib/tapestry/client';
export function useTapestryProfile(username: string) {
return useQuery({
queryKey: ['tapestry-profile', username],
queryFn: () => tapestryFetch<ProfileResponse>(`/profiles/${username}`),
enabled: !!username,
});
}
For detailed endpoint specs, see reference.md.
Profiles
GET /profiles/ - List/search profiles (by wallet, phone, twitter, email)POST /profiles/findOrCreate - Find or create profileGET /profiles/{id} - Get profile by ID or usernamePUT /profiles/{id} - Update profileGET /profiles/{id}/followers - Get followersGET /profiles/{id}/following - Get followingGET /profiles/{id}/followers/global - Cross-namespace followersGET /profiles/{id}/following/global - Cross-namespace followingGET /profiles/{id}/following-who-follow - Mutual connectionsGET /profiles/{id}/wallets - Get linked walletsPATCH /profiles/{id}/wallets - Link walletsDELETE /profiles/{id}/wallets - Unlink walletsPATCH /profiles/{id}/contacts - Link contactsDELETE /profiles/{id}/contacts - Unlink contactsPOST /profiles/{id}/notification - Send notificationGET /profiles/{id}/referrals - Get referral treeGET /profiles/{id}/suggested-profiles - Suggested followsGET /profiles/suggested/{identifier} - Suggestions by wallet/contactGET /profiles/suggested/{identifier}/global - Cross-namespace suggestionsGET /profiles/token-owners/{tokenAddress} - Token holder profilesGET /search/profiles - Search profiles by textSocial 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 propertiesPOST /contents/batch/read - Batch get by IDs (max 20)POST /contents/findOrCreate - Create content { id, profileId, properties: [{key,value}] }GET /contents/{id} - Get content detailsPUT /contents/{id} - Update content propertiesDELETE /contents/{id} - Delete contentComments
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 repliesPUT /comments/{id} - Update commentDELETE /comments/{id} - Delete commentGET /comments/{id}/replies - Get repliesLikes
GET /likes/{nodeId} - Get profiles who liked a nodePOST /likes/{nodeId} - Like { startId }DELETE /likes/{nodeId} - Unlike { startId }Activity Feeds
GET /activity/feed - User activity feed ?username=GET /activity/global - Global activity feedGET /activity/swap - Swap activity from followed wallets ?username=&tokenAddress=Identities
GET /identities/{id} - Resolve wallets/contacts from IDGET /identities/{id}/profiles - Find profiles across namespacesTrades
POST /trades/ - Log a tradeGET /trades/all-trades - Get all trades in time periodGET /trades/fetch-transaction-history - Wallet tx historyWallets
POST /wallets/{address}/connect - Connect two walletsGET /wallets/{address}/socialCounts - Social counts by walletAfter wallet connection + X login, call findOrCreate:
await tapestryFetch('/profiles/findOrCreate', {
method: 'POST',
body: {
username: twitterHandle, // from Privy OAuth
walletAddress: address, // from useUnifiedWallet
blockchain: 'SOLANA',
image: twitterProfileImage,
bio: twitterBio,
contact: { id: twitterHandle, type: 'TWITTER' },
},
});
await tapestryFetch('/followers/add', {
method: 'POST',
body: { startId: myProfileId, endId: targetProfileId },
});
await tapestryFetch('/contents/findOrCreate', {
method: 'POST',
body: {
id: `post-${Date.now()}`,
profileId: myProfileId,
properties: [
{ key: 'text', value: 'My post content' },
{ key: 'type', value: 'trade_call' },
],
},
});
await tapestryFetch(`/likes/${contentId}`, {
method: 'POST',
body: { startId: myProfileId },
});
await tapestryFetch('/comments/', {
method: 'POST',
body: {
profileId: myProfileId,
contentId: contentId,
text: 'Great trade!',
},
});
const feed = await tapestryFetch('/activity/feed', {
params: { username: myUsername },
});
const results = await tapestryFetch('/search/profiles', {
params: { query: searchText },
});
Store Polymarket-related data as content properties:
await tapestryFetch('/contents/findOrCreate', {
method: 'POST',
body: {
id: `prediction-${marketId}-${profileId}`,
profileId: myProfileId,
properties: [
{ key: 'type', value: 'prediction' },
{ key: 'marketId', value: polymarketConditionId },
{ key: 'position', value: 'YES' },
{ key: 'amount', value: 50 },
{ key: 'odds', value: 0.65 },
],
},
});
interface TapestryProfile {
id: string;
namespace: string;
created_at: number;
username: string;
bio: string | null;
image: string | null;
}
interface ProfileResponse {
profile: TapestryProfile;
walletAddress: string;
socialCounts: { followers: number; following: number };
namespace: { name: string | null; readableName: string | null };
}
interface ContentResponse {
id: string;
created_at: number;
properties: Record<string, any>;
}
interface CommentResponse {
comment: { id: string; created_at: number; text: string };
author: TapestryProfile;
socialCounts: { likeCount: number };
requestingProfileSocialInfo?: { hasLiked: boolean };
}
interface ActivityItem {
type: 'following' | 'new_content' | 'like' | 'comment' | 'new_follower';
actor_id: string;
actor_username: string;
target_id?: string;
target_username?: string;
timestamp: number;
activity: string;
}
interface PaginatedResponse<T> {
page: number;
pageSize: number;
totalCount: number;
data?: T[];
profiles?: T[];
activities?: T[];
}
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.
development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
development
Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.
development
Run, watch, debug, and extend OpenClaw QA testing with qa-lab and qa-channel. Use when Codex needs to execute the repo-backed QA suite, inspect live QA artifacts, debug failing scenarios, add new QA scenarios, or explain the OpenClaw QA workflow. Prefer the live OpenAI lane with regular openai/gpt-5.4 in fast mode; do not use gpt-5.4-pro or gpt-5.4-mini unless the user explicitly overrides that policy.
development
End-to-end Parallels smoke, upgrade, and rerun workflow for OpenClaw across macOS, Windows, and Linux guests. Use when Codex needs to run, rerun, debug, or interpret VM-based install, onboarding, gateway smoke tests, latest-release-to-main upgrade checks, fresh snapshot retests, or optional Discord roundtrip verification under Parallels.