From 5ec9bdd35ab000ba0c31354571777ba6eadf7af9 Mon Sep 17 00:00:00 2001 From: soheima Date: Tue, 18 Aug 2026 19:43:07 +0200 Subject: [PATCH 1/2] using sample token data --- app/demos/b20/B20Demo.tsx | 17 ++- .../b20/components/AnnouncementModule.tsx | 80 ---------- app/demos/b20/components/MemoModule.tsx | 73 +++++---- app/demos/b20/components/PolicyModule.tsx | 143 ++++++++++-------- app/demos/b20/lib/constants.ts | 10 +- app/demos/b20/lib/samples.test.ts | 24 +++ app/demos/b20/lib/samples.ts | 88 +++++++++-- 7 files changed, 235 insertions(+), 200 deletions(-) create mode 100644 app/demos/b20/lib/samples.test.ts diff --git a/app/demos/b20/B20Demo.tsx b/app/demos/b20/B20Demo.tsx index bb77ae6..59efce9 100644 --- a/app/demos/b20/B20Demo.tsx +++ b/app/demos/b20/B20Demo.tsx @@ -25,7 +25,7 @@ import { AnnouncementModule, SampleAnnouncementViewer } from './components/Annou import { DeployModule } from './components/DeployModule'; import { MemoModule } from './components/MemoModule'; import { PolicyModule } from './components/PolicyModule'; -import { client, CHAIN_ID, MODULES, SAMPLE_TOKEN } from './lib/constants'; +import { client, CHAIN_ID, MODULES } from './lib/constants'; import { b20Abi, b20Variant, @@ -40,6 +40,7 @@ import { shortAddress, } from './lib/protocol'; import { readRecent, readRecentPolicies, writeRecent, writeRecentPolicy } from './lib/recent'; +import { sampleTokenForAddress } from './lib/samples'; import type { ActivityItem, CreatedToken, @@ -102,7 +103,7 @@ export function B20Demo() { useEffect(() => { let cancelled = false; setIsOperator(false); - if (!activeTokenAddress || !wallet || activeTokenAddress.toLowerCase() === SAMPLE_TOKEN.toLowerCase()) return; + if (!activeTokenAddress || !wallet || sampleTokenForAddress(activeTokenAddress)) return; client .readContract({ address: activeTokenAddress, @@ -128,7 +129,7 @@ export function B20Demo() { setTokenAdminCheckedFor(null); if (!activeTokenAddress || !wallet) return; const checkKey = `${activeTokenAddress.toLowerCase()}:${wallet.toLowerCase()}`; - if (activeTokenAddress.toLowerCase() === SAMPLE_TOKEN.toLowerCase()) { + if (sampleTokenForAddress(activeTokenAddress)) { setTokenAdminCheckedFor(checkKey); return; } @@ -206,6 +207,14 @@ export function B20Demo() { const inspect = useCallback( async (candidate = tokenAddress) => { + const sampleToken = sampleTokenForAddress(candidate); + if (sampleToken) { + setInspectError(''); + setChecks(null); + setToken(sampleToken); + setTokenAddress(sampleToken.address); + return; + } if (!isAddress(candidate)) { setInspectError('Paste a valid token address, then try again.'); return; @@ -367,7 +376,7 @@ export function B20Demo() { trackB20ModuleSelect(next); }; const tokenAccess: TokenAccess = - token?.address.toLowerCase() === SAMPLE_TOKEN.toLowerCase() + sampleTokenForAddress(token?.address) ? 'sample' : isOperator ? 'operator' diff --git a/app/demos/b20/components/AnnouncementModule.tsx b/app/demos/b20/components/AnnouncementModule.tsx index f730c78..d315ec7 100644 --- a/app/demos/b20/components/AnnouncementModule.tsx +++ b/app/demos/b20/components/AnnouncementModule.tsx @@ -210,86 +210,6 @@ export function AnnouncementModule({ setError(walletErrorMessage(error)); } }; - if (token && tokenAccess === 'sample') - return ( -
- } - /> -
- -
-
- - Scheduled update · Mock data - - - 2027-Q1 forward split - - - This notice explains a two-for-one split and when wallets will show the new balance. - -
- 2027-Q1-split -
-
-
-
Token change
-
Displayed balances double
-
-
-
Effective
-
15 Jan 2027, 09:00 UTC
-
-
-
Recorded together
-
Notice and token change
-
-
-
- -
-
- - Information only · Mock data - - - Quarterly reserve attestation - - - This notice shares information without changing the token. - -
- 2026-Q4-reserves -
-
-
-
Token changes
-
None
-
-
-
Supporting link
-
https://example.com/disclosures/reserves
-
-
-
-
- -
- Ready to publish an announcement? - - Create an Asset token to write and publish your own announcements. - -
- -
-
- ); return (
- Sample transaction + Sample data · Read only - Transfer with memo + Memo history - A token transfer with a memo attached to help identify the transaction’s purpose. + Example token activity with short references attached to it. These records are local mock data.
- - View transaction ↗ - + + {SAMPLE_MEMOS.length} memo{SAMPLE_MEMOS.length === 1 ? '' : 's'} + +
+
+ {SAMPLE_MEMOS.map((row) => ( +
+
+
+

Memo

+

{row.memo}

+
+ + {row.operation} with memo + +
+
+
+
Memo caller
+
{shortAddress(row.caller)}
+
+
+
From
+
{shortAddress(row.from)}
+
+
+
To
+
{shortAddress(row.to)}
+
+
+
Amount
+
{formatAmount(row.value, token.decimals)} {token.symbol}
+
+
+
+ ))}
-
-
-
Operation
-
Transfer with a note
-
-
-
Amount
-
0.001 {token.symbol}
-
-
-
Memo
-
sending test
-
-
-
Encoding
-
bytes32
-
-

Create a token to add memos to your own transactions.

- ); return ( diff --git a/app/demos/b20/components/PolicyModule.tsx b/app/demos/b20/components/PolicyModule.tsx index 7e47f37..4125e8e 100644 --- a/app/demos/b20/components/PolicyModule.tsx +++ b/app/demos/b20/components/PolicyModule.tsx @@ -10,10 +10,10 @@ import { cn } from '../../../components/ui/cn'; import { InfoTooltip } from '../../../components/ui/InfoTooltip'; import { Text } from '../../../components/ui/Text'; import { VIBENET_EXPLORER_PATH } from '../../../vibenet/library/config'; -import { SAMPLE_TOKEN } from '../lib/constants'; import { B20_HELP, SCOPE_HELP } from '../lib/glossary'; import { formatAmount, MAX_SUPPLY_CAP, policyKindFromId, policyKindLabel, shortAddress } from '../lib/protocol'; import { READ_POLICY_PROMPT } from '../lib/prompts'; +import { SAMPLE_TOKEN } from '../lib/samples'; import type { CreatedPolicy, RecentPolicy, RecentToken, TokenAccess, TokenInfo } from '../lib/types'; import { AttachPolicy, type TokenAdminStatus } from './AttachPolicy'; import { CopyPromptButton } from './CopyPromptButton'; @@ -59,6 +59,7 @@ export function PolicyModule({ }) { const [showCreator, setShowCreator] = useState(false); const [suggestedPolicyId, setSuggestedPolicyId] = useState(null); + const isSample = tokenAccess === 'sample'; return (
@@ -71,11 +72,13 @@ export function PolicyModule({
- + {!isSample ? ( + + ) : null} - {showCreator ? ( + {showCreator && !isSample ? ( onInspect(SAMPLE_TOKEN)} + onClick={() => onInspect(SAMPLE_TOKEN.address)} disabled={busy === 'inspect'} > {busy === 'inspect' ? 'Loading…' : 'Explore the sample'} @@ -282,33 +285,37 @@ export function PolicyModule({ ))} - - -
- Check a wallet - {B20_HELP.checkAddress} -
- - See what this wallet can do with the token you selected. - -
- setCheckAddress(e.target.value)} - placeholder="Paste a wallet address" + {!isSample ? ( + <> + - -
-
+ +
+ Check a wallet + {B20_HELP.checkAddress} +
+ + See what this wallet can do with the token you selected. + +
+ setCheckAddress(e.target.value)} + placeholder="Paste a wallet address" + /> + +
+
+ + ) : null}
Token details @@ -322,41 +329,47 @@ export function PolicyModule({ value={token.cap === MAX_SUPPLY_CAP ? 'Unlimited' : formatAmount(token.cap, token.decimals)} /> - - View on Explorer ↗ - + {isSample ? ( +

Mock data · Not a deployed token

+ ) : ( + + View on Explorer ↗ + + )}
- -
-
- Technical reference - - These are the contract checks behind this screen. - -
- -
-
- {[ - 'factory.isB20(address)', - 'token.policyId(scope)', - 'registry.policyExists(id)', - 'registry.policyAdmin(id)', - 'registry.isAuthorized(id, account)', - ].map((item) => ( -
- {item} - Read + {!isSample ? ( + +
+
+ Technical reference + + These are the contract checks behind this screen. +
- ))} -
-
+ +
+
+ {[ + 'factory.isB20(address)', + 'token.policyId(scope)', + 'registry.policyExists(id)', + 'registry.policyAdmin(id)', + 'registry.isAuthorized(id, account)', + ].map((item) => ( +
+ {item} + Read +
+ ))} +
+ + ) : null}
) : null} diff --git a/app/demos/b20/lib/constants.ts b/app/demos/b20/lib/constants.ts index 56b56eb..ee1840e 100644 --- a/app/demos/b20/lib/constants.ts +++ b/app/demos/b20/lib/constants.ts @@ -1,4 +1,4 @@ -import { createPublicClient, http, isAddress, type Address } from 'viem'; +import { createPublicClient, http } from 'viem'; import { VIBENET_RPC_URL } from '../../../vibenet/library/config'; import type { Module } from './types'; @@ -10,14 +10,6 @@ export const client = createPublicClient({ transport: http(VIBENET_RPC_URL) }); export const STORAGE_KEY = 'vibenet.b20.recent.v1'; export const POLICY_STORAGE_KEY = 'vibenet.b20.recent-policies.v1'; -const configuredSampleToken = process.env.NEXT_PUBLIC_B20_SAMPLE_TOKEN; -export const SAMPLE_TOKEN = ( - configuredSampleToken && isAddress(configuredSampleToken) - ? configuredSampleToken - : '0xb200000000000000000000D7E62F6c2E13Ea9dDb' -) as Address; -export const SAMPLE_MEMO_TX = '0x91e52e0c63d05116b9fda41d1168c2fe9b7b9fcadf9071494c16410c809d1b09'; - export const INITIAL_ALLOCATION_MEMO = 'Initial deposit'; export const INITIAL_ALLOCATION_MAX = 100n; diff --git a/app/demos/b20/lib/samples.test.ts b/app/demos/b20/lib/samples.test.ts new file mode 100644 index 0000000..4f6ea06 --- /dev/null +++ b/app/demos/b20/lib/samples.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest'; + +import { POLICY_SCOPES } from './protocol'; +import { SAMPLE_MEMOS, SAMPLE_TOKEN, sampleTokenForAddress } from './samples'; + +describe('B20 sample data', () => { + it('resolves only the local sample token address, regardless of casing', () => { + expect(sampleTokenForAddress(SAMPLE_TOKEN.address)).toBe(SAMPLE_TOKEN); + expect(sampleTokenForAddress(SAMPLE_TOKEN.address.toUpperCase())).toBe(SAMPLE_TOKEN); + expect(sampleTokenForAddress('0x1111111111111111111111111111111111111111')).toBeNull(); + expect(sampleTokenForAddress(undefined)).toBeNull(); + }); + + it('provides a complete read-only policy and memo walkthrough', () => { + expect(SAMPLE_TOKEN.policies.map((policy) => policy.scope)).toEqual(POLICY_SCOPES.map(([scope]) => scope)); + expect(SAMPLE_TOKEN.policies.filter((policy) => policy.id !== 0n)).toHaveLength(2); + expect(SAMPLE_TOKEN.policies.every((policy) => policy.exists)).toBe(true); + expect(SAMPLE_MEMOS).toEqual( + expect.arrayContaining([ + expect.objectContaining({ operation: 'transfer', memo: 'sending test', value: 1_000_000_000_000_000n }), + ]), + ); + }); +}); diff --git a/app/demos/b20/lib/samples.ts b/app/demos/b20/lib/samples.ts index 8d0b5b2..4baee2f 100644 --- a/app/demos/b20/lib/samples.ts +++ b/app/demos/b20/lib/samples.ts @@ -2,6 +2,83 @@ // token. Kept out of the components so the copy is easy to review/revise in one // place and the render functions stay presentational. +import type { Address } from 'viem'; + +import type { TokenInfo } from './types'; + +const SAMPLE_POLICY_ADMIN = '0x1111111111111111111111111111111111111111' as Address; + +// This is intentionally a B20-shaped, synthetic address. It identifies the +// local walkthrough only; it is not a deployed contract and must never be read +// from the public RPC. +export const SAMPLE_TOKEN: TokenInfo = { + address: '0xb200000000000000000000D7E62F6c2E13Ea9dDb' as Address, + name: 'Vibenet Reserve Asset', + symbol: 'VRA', + decimals: 18, + variant: 'asset', + supply: 2_500_000n * 10n ** 18n, + cap: 5_000_000n * 10n ** 18n, + contractURI: 'https://example.com/tokens/vibenet-reserve-asset', + policies: [ + { + scope: 'TRANSFER_SENDER_POLICY', + label: 'Transfer sender', + id: (1n << 56n) + 42n, + exists: true, + admin: SAMPLE_POLICY_ADMIN, + }, + { + scope: 'TRANSFER_RECEIVER_POLICY', + label: 'Transfer receiver', + id: 0n, + exists: true, + admin: SAMPLE_POLICY_ADMIN, + }, + { + scope: 'TRANSFER_EXECUTOR_POLICY', + label: 'Transfer executor', + id: 0n, + exists: true, + admin: SAMPLE_POLICY_ADMIN, + }, + { + scope: 'MINT_RECEIVER_POLICY', + label: 'Mint recipient', + id: 7n, + exists: true, + admin: SAMPLE_POLICY_ADMIN, + }, + ], +}; + +export type SampleMemo = { + id: string; + operation: 'transfer' | 'mint'; + memo: string; + caller: Address; + from: Address; + to: Address; + value: bigint; +}; + +export const SAMPLE_MEMOS: SampleMemo[] = [ + { + id: 'reserve-transfer-001', + operation: 'transfer', + memo: 'sending test', + caller: SAMPLE_POLICY_ADMIN, + from: SAMPLE_POLICY_ADMIN, + to: '0x2222222222222222222222222222222222222222' as Address, + value: 1_000_000_000_000_000n, + }, +]; + +/** Returns the local walkthrough token without initiating a network read. */ +export function sampleTokenForAddress(candidate: string | null | undefined): TokenInfo | null { + return candidate?.toLowerCase() === SAMPLE_TOKEN.address.toLowerCase() ? SAMPLE_TOKEN : null; +} + export type SampleAnnouncement = { id: string; type: string; @@ -13,20 +90,11 @@ export type SampleAnnouncement = { }; export const SAMPLE_ANNOUNCEMENTS: SampleAnnouncement[] = [ - { - id: '2027-Q1-split', - type: 'Scheduled token update', - title: '2027 Q1 Forward Split', - description: 'A two-for-one split that doubles the tokens shown in wallets. It does not set the token price.', - uri: 'https://example.com/disclosures/2027-q1-split', - effective: '15 Jan 2027, 09:00 UTC', - call: 'Displayed balances double', - }, { id: '2026-Q4-reserves', type: 'Disclosure only', title: 'Quarterly Reserve Attestation', - description: 'The issuer shared its quarterly reserve report without changing the token.', + description: 'The issuer shared its quarterly reserve report.', uri: 'https://example.com/disclosures/2026-q4-reserves', effective: 'Published 20 Dec 2026', call: 'No token change', From 5ce6e3eb0fadf8fb610571bad5e58dbef1dc2eb1 Mon Sep 17 00:00:00 2001 From: soheima Date: Tue, 18 Aug 2026 19:47:22 +0200 Subject: [PATCH 2/2] removed sample token data --- app/demos/b20/B20Demo.tsx | 5 ++--- app/demos/b20/lib/protocol.ts | 1 - app/demos/b20/lib/samples.ts | 1 - app/demos/b20/lib/types.ts | 1 - 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/demos/b20/B20Demo.tsx b/app/demos/b20/B20Demo.tsx index 59efce9..34d3d65 100644 --- a/app/demos/b20/B20Demo.tsx +++ b/app/demos/b20/B20Demo.tsx @@ -244,13 +244,12 @@ export function B20Demo() { if (!isB20 || !initialized) throw new Error('This address is not a ready-to-use B20 token.'); const variant = b20Variant(address); if (!variant) throw new Error('We could not identify this B20 token type.'); - const [name, symbol, decimals, supply, cap, contractURI, policyRows] = await Promise.all([ + const [name, symbol, decimals, supply, cap, policyRows] = await Promise.all([ client.readContract({ address, abi: b20Abi, functionName: 'name' }), client.readContract({ address, abi: b20Abi, functionName: 'symbol' }), client.readContract({ address, abi: b20Abi, functionName: 'decimals' }), client.readContract({ address, abi: b20Abi, functionName: 'totalSupply' }), client.readContract({ address, abi: b20Abi, functionName: 'supplyCap' }), - client.readContract({ address, abi: b20Abi, functionName: 'contractURI' }), Promise.all( POLICY_SCOPES.map(async ([scope, label]) => { const id = await client.readContract({ @@ -277,7 +276,7 @@ export function B20Demo() { }), ), ]); - setToken({ address, name, symbol, decimals, variant, supply, cap, contractURI, policies: policyRows }); + setToken({ address, name, symbol, decimals, variant, supply, cap, policies: policyRows }); setTokenAddress(address); } catch (error) { setToken(null); diff --git a/app/demos/b20/lib/protocol.ts b/app/demos/b20/lib/protocol.ts index 80f3e2a..f9dde53 100644 --- a/app/demos/b20/lib/protocol.ts +++ b/app/demos/b20/lib/protocol.ts @@ -154,7 +154,6 @@ export const b20Abi = [ { type: 'function', name: 'decimals', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint8' }] }, { type: 'function', name: 'totalSupply', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] }, { type: 'function', name: 'supplyCap', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] }, - { type: 'function', name: 'contractURI', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] }, { type: 'function', name: 'balanceOf', diff --git a/app/demos/b20/lib/samples.ts b/app/demos/b20/lib/samples.ts index 4baee2f..edfb364 100644 --- a/app/demos/b20/lib/samples.ts +++ b/app/demos/b20/lib/samples.ts @@ -19,7 +19,6 @@ export const SAMPLE_TOKEN: TokenInfo = { variant: 'asset', supply: 2_500_000n * 10n ** 18n, cap: 5_000_000n * 10n ** 18n, - contractURI: 'https://example.com/tokens/vibenet-reserve-asset', policies: [ { scope: 'TRANSFER_SENDER_POLICY', diff --git a/app/demos/b20/lib/types.ts b/app/demos/b20/lib/types.ts index 62ebcc7..3e73a8a 100644 --- a/app/demos/b20/lib/types.ts +++ b/app/demos/b20/lib/types.ts @@ -17,7 +17,6 @@ export type RecentToken = { export type TokenInfo = RecentToken & { supply: bigint; cap: bigint; - contractURI: string; policies: Array<{ scope: string; label: string; id: bigint; exists: boolean; admin: Address }>; };