bloby-bot 0.27.3 → 0.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bloby-bot",
3
- "version": "0.27.3",
3
+ "version": "0.28.1",
4
4
  "releaseNotes": [
5
5
  "1. # voice note (PTT bubble)",
6
6
  "2. # audio file + caption",
@@ -0,0 +1,129 @@
1
+ // One-off Tempo USDC transfer.
2
+ //
3
+ // Fill in the CONFIG block below, then run:
4
+ // /tmp/tempo-transfer/node_modules/.bin/tsx scripts/tempo-transfer.ts
5
+ //
6
+ // (The repo's node_modules has a @noble/hashes version conflict that breaks
7
+ // viem here, so the script runs from an isolated install at /tmp/tempo-transfer.
8
+ // To rebuild that dir if it disappears:
9
+ // mkdir -p /tmp/tempo-transfer && cd /tmp/tempo-transfer \
10
+ // && npm init -y && npm install viem@2.47.6 tsx)
11
+
12
+ // ─── CONFIG ──────────────────────────────────────────────────────────────────
13
+
14
+ const FROM = {
15
+ privateKey: '82b62a625382eef1007a77d6eb3d848a9d1df0c9b59d2bd578893962003c17e2',
16
+ wallet: '0x084A80e395FaE8Aaf58F591f47F63DA1EeF06bbC',
17
+ };
18
+
19
+ const TO = {
20
+ privateKey: '0x0a5194196773b67790f0da564337929b842c1c17f9c445b6c449205dbd426a49',
21
+ wallet: '0xc65d8a0dB80f637337B87e42b8BEb5D86D46f942',
22
+ };
23
+
24
+ const AMOUNT_USD = '1';
25
+
26
+ // ─────────────────────────────────────────────────────────────────────────────
27
+
28
+ import {
29
+ createPublicClient,
30
+ createWalletClient,
31
+ http,
32
+ formatUnits,
33
+ parseUnits,
34
+ } from 'viem';
35
+ import { privateKeyToAccount } from 'viem/accounts';
36
+ import { tempo } from 'viem/chains';
37
+
38
+ const USDC = '0x20c000000000000000000000b9537d11c60e8b50' as const;
39
+ const DECIMALS = 6;
40
+ const RPC = 'https://rpc.tempo.xyz';
41
+
42
+ const erc20Abi = [
43
+ {
44
+ name: 'balanceOf',
45
+ type: 'function',
46
+ stateMutability: 'view',
47
+ inputs: [{ name: 'account', type: 'address' }],
48
+ outputs: [{ name: '', type: 'uint256' }],
49
+ },
50
+ {
51
+ name: 'transfer',
52
+ type: 'function',
53
+ stateMutability: 'nonpayable',
54
+ inputs: [
55
+ { name: 'to', type: 'address' },
56
+ { name: 'amount', type: 'uint256' },
57
+ ],
58
+ outputs: [{ name: '', type: 'bool' }],
59
+ },
60
+ ] as const;
61
+
62
+ function normalizePk(pk: string): `0x${string}` {
63
+ const trimmed = pk.trim();
64
+ return (trimmed.startsWith('0x') ? trimmed : `0x${trimmed}`) as `0x${string}`;
65
+ }
66
+
67
+ async function main() {
68
+ const account = privateKeyToAccount(normalizePk(FROM.privateKey));
69
+ const to = TO.wallet as `0x${string}`;
70
+ const amount = parseUnits(AMOUNT_USD, DECIMALS);
71
+
72
+ if (account.address.toLowerCase() !== FROM.wallet.toLowerCase()) {
73
+ console.error(`FROM.privateKey derives ${account.address}, but FROM.wallet is ${FROM.wallet}`);
74
+ process.exit(1);
75
+ }
76
+
77
+ const publicClient = createPublicClient({ chain: tempo, transport: http(RPC) });
78
+ const walletClient = createWalletClient({ account, chain: tempo, transport: http(RPC) });
79
+
80
+ console.log(`From: ${account.address}`);
81
+ console.log(`To: ${to}`);
82
+ console.log(`Amount: ${AMOUNT_USD} USDC (${amount} base units)`);
83
+
84
+ const balanceBefore = await publicClient.readContract({
85
+ address: USDC,
86
+ abi: erc20Abi,
87
+ functionName: 'balanceOf',
88
+ args: [account.address],
89
+ });
90
+ console.log(`Sender balance before: ${formatUnits(balanceBefore, DECIMALS)} USDC`);
91
+
92
+ if (balanceBefore < amount) {
93
+ console.error('Insufficient balance');
94
+ process.exit(1);
95
+ }
96
+
97
+ const hash = await walletClient.writeContract({
98
+ address: USDC,
99
+ abi: erc20Abi,
100
+ functionName: 'transfer',
101
+ args: [to, amount],
102
+ });
103
+ console.log(`Tx hash: ${hash}`);
104
+
105
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
106
+ console.log(`Status: ${receipt.status} Block: ${receipt.blockNumber}`);
107
+
108
+ const [balanceFromAfter, balanceToAfter] = await Promise.all([
109
+ publicClient.readContract({
110
+ address: USDC,
111
+ abi: erc20Abi,
112
+ functionName: 'balanceOf',
113
+ args: [account.address],
114
+ }),
115
+ publicClient.readContract({
116
+ address: USDC,
117
+ abi: erc20Abi,
118
+ functionName: 'balanceOf',
119
+ args: [to],
120
+ }),
121
+ ]);
122
+ console.log(`Sender balance after: ${formatUnits(balanceFromAfter, DECIMALS)} USDC`);
123
+ console.log(`Recipient balance after: ${formatUnits(balanceToAfter, DECIMALS)} USDC`);
124
+ }
125
+
126
+ main().catch((err) => {
127
+ console.error(err);
128
+ process.exit(1);
129
+ });
@@ -0,0 +1,115 @@
1
+ // End-to-end test for the test-mpp service on the relay.
2
+ //
3
+ // Fill in the CONFIG block, then run from the isolated install dir
4
+ // (same workaround as tempo-transfer.ts — repo node_modules has a
5
+ // @noble/hashes conflict that breaks viem here):
6
+ //
7
+ // mkdir -p /tmp/tempo-transfer && cd /tmp/tempo-transfer \
8
+ // && npm init -y && npm install viem@2.47.6 mppx tsx
9
+ // /tmp/tempo-transfer/node_modules/.bin/tsx scripts/test-mpp-call.ts
10
+ //
11
+ // What it does:
12
+ // 1. Reads on-chain USDC balance for the bot wallet on Tempo.
13
+ // 2. POSTs to /api/services/test-mpp/use through the mppx client,
14
+ // which auto-handles the 402 → sign USDC transfer → retry flow.
15
+ // 3. Prints status, body, Payment-Receipt header, and the new balance.
16
+ //
17
+ // Expected first-run output (with $0 account balance, $1 wallet):
18
+ // paidVia=mpp · balance drops by $0.01 · Payment-Receipt header present.
19
+
20
+ // ─── CONFIG ──────────────────────────────────────────────────────────────────
21
+
22
+ const RELAY = 'https://api.bloby.bot';
23
+
24
+ const BOT = {
25
+ // Bot's relay token — the value from `bloby init` / config.json `relay.token`.
26
+ // Used as `X-Bloby-Token` header so identity survives the mppx 402 retry.
27
+ relayToken: '<FILL_ME — 64-hex-char relay token>',
28
+
29
+ // Bot's USDC wallet on Tempo. Must match the `walletAddress` registered
30
+ // with the relay (so account-balance lookups go through the right user).
31
+ privateKey: '0x0a5194196773b67790f0da564337929b842c1c17f9c445b6c449205dbd426a49',
32
+ wallet: '0xc65d8a0dB80f637337B87e42b8BEb5D86D46f942',
33
+ };
34
+
35
+ // ─────────────────────────────────────────────────────────────────────────────
36
+
37
+ import { Mppx, tempo } from 'mppx/client';
38
+ import { privateKeyToAccount } from 'viem/accounts';
39
+ import { createPublicClient, http, formatUnits } from 'viem';
40
+ import { tempo as tempoChain } from 'viem/chains';
41
+
42
+ const USDC = '0x20c000000000000000000000b9537d11c60e8b50' as const;
43
+ const RPC = 'https://rpc.tempo.xyz';
44
+
45
+ const erc20Abi = [{
46
+ name: 'balanceOf',
47
+ type: 'function',
48
+ stateMutability: 'view',
49
+ inputs: [{ name: 'account', type: 'address' }],
50
+ outputs: [{ name: '', type: 'uint256' }],
51
+ }] as const;
52
+
53
+ function normalizePk(pk: string): `0x${string}` {
54
+ const t = pk.trim();
55
+ return (t.startsWith('0x') ? t : `0x${t}`) as `0x${string}`;
56
+ }
57
+
58
+ async function main() {
59
+ if (BOT.relayToken.startsWith('<')) {
60
+ console.error('Fill in BOT.relayToken before running.');
61
+ process.exit(1);
62
+ }
63
+
64
+ const account = privateKeyToAccount(normalizePk(BOT.privateKey));
65
+ if (account.address.toLowerCase() !== BOT.wallet.toLowerCase()) {
66
+ console.error(`privateKey derives ${account.address}, but BOT.wallet is ${BOT.wallet}`);
67
+ process.exit(1);
68
+ }
69
+
70
+ const publicClient = createPublicClient({ chain: tempoChain, transport: http(RPC) });
71
+
72
+ const before = await publicClient.readContract({
73
+ address: USDC, abi: erc20Abi, functionName: 'balanceOf', args: [account.address],
74
+ });
75
+ console.log(`Wallet: ${account.address}`);
76
+ console.log(`Balance before: ${formatUnits(before, 6)} USDC`);
77
+
78
+ // mppx client: do NOT polyfill global fetch — only the request through
79
+ // mppx.fetch(...) gets the auto-pay-on-402 behavior.
80
+ const mppx = Mppx.create({
81
+ polyfill: false,
82
+ methods: [tempo({ account })],
83
+ });
84
+
85
+ const url = `${RELAY}/api/services/test-mpp/use`;
86
+ console.log(`POST ${url}`);
87
+
88
+ const res = await mppx.fetch(url, {
89
+ method: 'POST',
90
+ headers: {
91
+ 'X-Bloby-Token': BOT.relayToken,
92
+ 'Content-Type': 'application/json',
93
+ },
94
+ body: JSON.stringify({}),
95
+ });
96
+
97
+ const body = await res.text();
98
+ const receipt = res.headers.get('Payment-Receipt');
99
+
100
+ console.log(`Status: ${res.status}`);
101
+ console.log(`Body: ${body}`);
102
+ console.log(`Payment-Receipt: ${receipt ?? '(none — paid via balance or free)'}`);
103
+
104
+ const after = await publicClient.readContract({
105
+ address: USDC, abi: erc20Abi, functionName: 'balanceOf', args: [account.address],
106
+ });
107
+ console.log(`Balance after: ${formatUnits(after, 6)} USDC`);
108
+ const delta = before - after;
109
+ console.log(`Delta: -${formatUnits(delta, 6)} USDC`);
110
+ }
111
+
112
+ main().catch((err) => {
113
+ console.error(err);
114
+ process.exit(1);
115
+ });
@@ -292,13 +292,52 @@ All skills on the official marketplace are created or audited by the Bloby team
292
292
 
293
293
  During PULSE, occasionally check the marketplace for new skills. If you spot one that fits what your human does or is trying to achieve, suggest it.
294
294
 
295
- ### Wallet & Balance (Coming Soon)
295
+ ### Money & Payments
296
296
 
297
- ### Buying with Your Human Credits
298
- You can spend money if your human has claimed you on the Bloby Website Dashboard and he has Credits.
299
- GET | `/api/marketplace/balance/bot` | Bearer (required) | Check your owner's credit balance
297
+ You have **two payment sources** for paid relay endpoints (skills, services, marketplace):
300
298
 
301
- Agents will soon have a USDC wallet for autonomous purchases. You'll be able to check your balance and buy skills directly without a redeem code. This is not yet available for now, paid skills require your human to purchase from the web marketplace and give you a redeem code.
299
+ - **Your human's credits** money they added via Stripe on the dashboard. Only available if they've claimed you. Shared across all their bots.
300
+ - **Your wallet** — your own USDC on the Tempo network. Autonomous — no permission needed. Funded by your human via the "Add Funds" button (Stripe converts their card payment to USDC and sends it to your address).
301
+
302
+ **Payment order is automatic.** The relay tries credits first. If the human's balance covers the call, credits get deducted and your wallet stays untouched. If credits are short (or you're unclaimed), the call falls back to **MPP**: the relay responds `402 Payment Required`, your client signs a USDC transfer from your wallet, and the call retries — all transparently.
303
+
304
+ **Check your wallet (own funds, on-chain):**
305
+
306
+ ```bash
307
+ curl -s http://localhost:7400/api/wallet/balance
308
+ # → { "address": "0x...", "balance": "0.99" } # balance is in USDC dollars
309
+ ```
310
+
311
+ **Check your human's credits (claimed only):**
312
+
313
+ ```bash
314
+ curl -s https://api.bloby.bot/api/marketplace/balance/bot \
315
+ -H "Authorization: Bearer $RELAY_TOKEN"
316
+ # → { "balance": 12.34 }
317
+ ```
318
+
319
+ **Calling a paid service.** Use `X-Bloby-Token: $RELAY_TOKEN` *instead of* `Authorization: Bearer ...` — the MPP client strips the Authorization header on its 402 retry to inject the payment credential, so bot identity needs a separate header.
320
+
321
+ ```bash
322
+ # Smoke test — costs $0.01, returns a confirmation phrase
323
+ curl -s -X POST https://api.bloby.bot/api/services/test-mpp/use \
324
+ -H "X-Bloby-Token: $RELAY_TOKEN"
325
+ # → "PINEAPPLE-MPP-OK · paid via {free|balance|mpp} · 2026-…"
326
+ ```
327
+
328
+ For services that may fall back to MPP, use the `mppx` CLI to auto-handle the 402 → sign → retry loop. **The mppx CLI uses its own OS-keychain by default and does NOT know about your wallet** — pass your private key inline via `MPPX_PRIVATE_KEY` (which mppx checks before the keychain):
329
+
330
+ ```bash
331
+ MPPX_PRIVATE_KEY=$(jq -r .wallet.privateKey ~/.bloby/config.json) \
332
+ npx -y mppx http://api.bloby.bot/api/services/test-mpp/use \
333
+ -X POST -H "X-Bloby-Token: $RELAY_TOKEN"
334
+ ```
335
+
336
+ If you see `ACCOUNT_NOT_FOUND - No account found.`, you forgot to set `MPPX_PRIVATE_KEY` — the CLI looked in its empty keychain. Re-run with the env var.
337
+
338
+ **Wallet on disk:** `~/.bloby/config.json` field `wallet` — `address` is public, `privateKey` is secret. Never print the private key in chat. The address is fine to share.
339
+
340
+ **Running low?** Tell your human you need funds. Don't mention USDC, Tempo, or crypto unless they ask — to them, it's just adding dollars to your wallet via the "Add Funds" button in the chat header.
302
341
 
303
342
  For a machine-readable catalog: `GET https://bloby.bot/api/marketplace/products` — **always include your relay token** (`Authorization: Bearer $RELAY_TOKEN`) on every request to the relay API, even public endpoints. This enables your presence in Bloby World so other users can see your bloby on the world map.
304
343