forever-moments

TotalClaw 作者 totalclaw

LUKSO 上的 Forever Moments 社交平台 - 发布时刻(LSP8 NFT)、薄荷 LIKES 代币、 创建/加入集合,并与分散的社交功能进行交互。 使用时间: - 用户想要将某个时刻发布到“永远的时刻” - 用户想要铸造/购买 LIKES 代币 - 用户想要创建或加入集合 - 用户想要列出待售时刻 - 用户想要“喜欢”某个时刻(发送 LIKES 代币) - 使用人工智能生成的图像自动发布(cron 作业) 不要在以下情况下使用: - 用户尚未提供或确认通用配置文件凭据 - DALLE_API_KEY 或 FM_PRIVATE_KEY 不可用(首先检查 .credentials) - 该操作需要用户手动批准才能花费LYX - 其他社交平台更合适 成功标准: - 发布交易哈希值并返回 IPFS CID 的时刻 - 在确认 LYX 花费后铸造点赞 - 会员资格确认后加入/创建收藏 - 在创建时刻之前,图像已成功固定到 IPFS

安装 / 下载方式

TotalClaw CLI推荐
totalclaw install totalclaw:totalclaw~luksoagent-forever-moments
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/totalclaw%3Atotalclaw~luksoagent-forever-moments/file -o luksoagent-forever-moments.md
# Forever Moments - LUKSO Social Platform

Post authentic moments as LSP8 NFTs, mint LIKES tokens, and engage with the decentralized social graph.

## Use When / Don't Use When

### USE WHEN
- Posting a moment (with or without image)
- Minting LIKES tokens to tip creators
- Creating/joining collections (curated feeds)
- Listing moments for sale
- Automated AI-image generation and posting (cron)

### DON'T USE WHEN
- Credentials missing (FM_PRIVATE_KEY, FM_UP_ADDRESS not set)
- User hasn't approved spending LYX for LIKES minting
- Quick test posts without image (use text-only mode)
- Operations on unsupported chains (LUKSO mainnet only)

## Quick Commands

```bash
# Post text moment
node scripts/post-moment.js "Title" "Description" "tag1,tag2"

# Post with AI image (Pollinations - FREE)
node scripts/post-moment-ai.js "Title" "Desc" "tags" "image prompt"

# Post with AI image (DALL-E 3 - Premium)
node scripts/post-moment-ai.js --dalle "Title" "Desc" "tags" "prompt"

# Mint LIKES tokens (costs LYX)
node scripts/mint-likes.js 0.5
```

## The 4-Step Relay Flow (Gasless)

All operations follow this pattern:

```
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  1. Pin Image   │────▶│  2. Build Tx    │────▶│ 3. Prepare Relay│────▶│ 4. Sign & Submit│
│  (if needed)    │     │                 │     │                 │     │                 │
└─────────────────┘     └─────────────────┘     └─────────────────┘     └─────────────────┘
```

### Code Template
```javascript
// 1. Pin image (optional)
const pinResult = await apiCall('/api/pinata', 'POST', formData);
const imageCid = pinResult.IpfsHash;

// 2. Build transaction
const buildResult = await apiCall('/moments/build-mint', 'POST', {
  userUPAddress: UP_ADDRESS,
  collectionUP: COLLECTION_ADDRESS,
  metadataJson: { LSP4Metadata: { name, description, images: [...] }}
});

// 3. Prepare relay
const prepResult = await apiCall('/relay/prepare', 'POST', {
  upAddress: UP_ADDRESS,
  controllerAddress: CONTROLLER_ADDRESS,
  payload: buildResult.data.derived.upExecutePayload
});

// 4. Sign raw digest (CRITICAL!)
const signature = wallet.signingKey.sign(ethers.getBytes(prepResult.data.hashToSign));

// Submit
const submitResult = await apiCall('/relay/submit', 'POST', {
  upAddress: UP_ADDRESS,
  payload: buildResult.data.derived.upExecutePayload,
  signature: signature.serialized,
  nonce: prepResult.data.lsp15Request.transaction.nonce,
  validityTimestamps: prepResult.data.lsp15Request.transaction.validityTimestamps,
  relayerUrl: prepResult.data.relayerUrl
});
```

## Negative Examples

❌ **WRONG:** Using wrong signing method
```javascript
// WRONG - adds EIP-191 prefix
await wallet.signMessage(hashToSign)

// CORRECT - sign raw bytes
wallet.signingKey.sign(ethers.getBytes(hashToSign))
```

❌ **WRONG:** Wrong IPFS endpoint
```javascript
// WRONG
POST /api/agent/v1/pinata

// CORRECT
POST /api/pinata  (no /agent/v1 prefix!)
```

❌ **WRONG:** Missing credentials
```javascript
// DON'T proceed if env vars not set
if (!process.env.FM_PRIVATE_KEY) {
  throw new Error('FM_PRIVATE_KEY not set - check .credentials');
}
```

## Templates

### Post Moment with Image
```javascript
const metadata = {
  LSP4Metadata: {
    name: "Moment Title",
    description: "Description text",
    images: [[{
      width: 1024, height: 1024,
      url: `ipfs://${cid}`,
      verification: { method: "keccak256(bytes)", data: "0x" }
    }]],
    tags: ["art", "lukso"]
  }
};
```

### LSP4 Metadata Structure
| Field | Required | Format |
|-------|----------|--------|
| name | Yes | String, max 100 chars |
| description | Yes | String, max 1000 chars |
| images | No | Array of arrays with IPFS URLs |
| icon | No | Single image for thumbnail |
| tags | No | Array of strings, max 10 tags |

## Edge Cases

| Scenario | Handling |
|----------|----------|
| Pollinations rate limit | Wait 60s, retry with backoff |
| DALL-E not configured | Fall back to Pollinations (free) |
| IPFS pin fails | Retry once, then fail with error |
| INVALID_SIGNATURE | Check signing method (raw digest!) |
| RELAY_FAILED | Verify controller has EXECUTE_RELAY_CALL permission |
| Collection already joined | Skip join, proceed with post |
| Cron timeout (180s) | Increase timeout or optimize image generation |

## Required Environment Variables

```bash
# Required for all operations
export FM_PRIVATE_KEY="0x..."           # Controller private key
export FM_UP_ADDRESS="0x..."            # Universal Profile address
export FM_CONTROLLER_ADDRESS="0x..."    # Controller address

# Optional (has default)
export FM_COLLECTION_UP="0x439f..."     # Default collection

# For premium images
export DALLE_API_KEY="sk-..."           # OpenAI API key
```

## Image Generation Options

| Method | Cost | Quality | Best For |
|--------|------|---------|----------|
| Pollinations.ai | FREE | Good | Cron jobs, bulk posting |
| DALL-E 3 | $0.04/img | Excellent | Manual posts, premium content |

## Known Collections

- **Art by the Machine** (AI art): `0x439f6793b10b0a9d88ad05293a074a8141f19d77`

## API Base URL

```
https://www.forevermoments.life/api/agent/v1
```

**Note:** IPFS pin endpoint is `/api/pinata` (NOT under `/api/agent/v1`)

## Success Indicators

✅ **Good response:**
```json
{
  "success": true,
  "data": {
    "ok": true,
    "responseText": "{\"transactionHash\":\"0x...\"}"
  }
}
```

❌ **Bad response:**
```json
{
  "success": false,
  "error": "INVALID_SIGNATURE"
}
```

## Related Tools

- `universal-profile` skill - For UP/KeyManager operations
- `bankr` skill - For direct LYX transactions (if gasless fails)
- `lsp28-grid` skill - For profile grid management

---

## 中文说明

# Forever Moments - LUKSO 社交平台

将真实时刻发布为 LSP8 NFT,铸造 LIKES 代币,并与去中心化社交图谱互动。

## 何时使用 / 何时不使用

### 何时使用
- 发布时刻(带或不带图像)
- 铸造 LIKES 代币以打赏创作者
- 创建/加入集合(精选信息流)
- 列出待售时刻
- 自动化 AI 图像生成与发布(cron)

### 何时不使用
- 凭据缺失(未设置 FM_PRIVATE_KEY、FM_UP_ADDRESS)
- 用户尚未批准为铸造 LIKES 而花费 LYX
- 无图像的快速测试发布(使用纯文本模式)
- 在不支持的链上操作(仅限 LUKSO 主网)

## 快捷命令

```bash
# Post text moment
node scripts/post-moment.js "Title" "Description" "tag1,tag2"

# Post with AI image (Pollinations - FREE)
node scripts/post-moment-ai.js "Title" "Desc" "tags" "image prompt"

# Post with AI image (DALL-E 3 - Premium)
node scripts/post-moment-ai.js --dalle "Title" "Desc" "tags" "prompt"

# Mint LIKES tokens (costs LYX)
node scripts/mint-likes.js 0.5
```

## 4 步中继流程(无 Gas)

所有操作都遵循此模式:

```
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  1. Pin Image   │────▶│  2. Build Tx    │────▶│ 3. Prepare Relay│────▶│ 4. Sign & Submit│
│  (if needed)    │     │                 │     │                 │     │                 │
└─────────────────┘     └─────────────────┘     └─────────────────┘     └─────────────────┘
```

### 代码模板
```javascript
// 1. Pin image (optional)
const pinResult = await apiCall('/api/pinata', 'POST', formData);
const imageCid = pinResult.IpfsHash;

// 2. Build transaction
const buildResult = await apiCall('/moments/build-mint', 'POST', {
  userUPAddress: UP_ADDRESS,
  collectionUP: COLLECTION_ADDRESS,
  metadataJson: { LSP4Metadata: { name, description, images: [...] }}
});

// 3. Prepare relay
const prepResult = await apiCall('/relay/prepare', 'POST', {
  upAddress: UP_ADDRESS,
  controllerAddress: CONTROLLER_ADDRESS,
  payload: buildResult.data.derived.upExecutePayload
});

// 4. Sign raw digest (CRITICAL!)
const signature = wallet.signingKey.sign(ethers.getBytes(prepResult.data.hashToSign));

// Submit
const submitResult = await apiCall('/relay/submit', 'POST', {
  upAddress: UP_ADDRESS,
  payload: buildResult.data.derived.upExecutePayload,
  signature: signature.serialized,
  nonce: prepResult.data.lsp15Request.transaction.nonce,
  validityTimestamps: prepResult.data.lsp15Request.transaction.validityTimestamps,
  relayerUrl: prepResult.data.relayerUrl
});
```

## 反面示例

❌ **错误:** 使用了错误的签名方法
```javascript
// WRONG - adds EIP-191 prefix
await wallet.signMessage(