Skip to content

Commit 4c35073

Browse files
Wieedzeclaude
andcommitted
demo: standalone Coolify deploy + switch to createTriplesVia
The 'useless dApp' now creates a triple from three existing atom IDs instead of createAtomsVia, so it needs no IPFS pinning (pinMutation is off on mainnet) and always renders. Adds a 'Load 3 from chain' helper that reads recent AtomCreated logs (no indexer), and a self-contained caddy:2-alpine Dockerfile + Caddyfile so Coolify can serve demo-dapp/ on its own domain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 55f5f71 commit 4c35073

3 files changed

Lines changed: 76 additions & 15 deletions

File tree

demo-dapp/Caddyfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Serve the single-page demo on :80 (Coolify's proxy terminates TLS in front).
2+
# No try_files rewrite needed — there is no client-side router, just one file.
3+
:80 {
4+
root * /srv
5+
encode gzip
6+
file_server
7+
}

demo-dapp/Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# syntax=docker/dockerfile:1
2+
3+
# Static single-file demo — no build step. The page imports viem from esm.sh
4+
# at runtime, so there is nothing to bundle: just serve index.html with Caddy.
5+
# In Coolify, point the app at this directory (Base Directory = /demo-dapp,
6+
# Dockerfile = Dockerfile) and set the demo's own domain — Coolify terminates
7+
# TLS in front.
8+
FROM caddy:2-alpine
9+
COPY Caddyfile /etc/caddy/Caddyfile
10+
COPY index.html /srv/index.html
11+
EXPOSE 80

demo-dapp/index.html

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@
2222
.primary { background: var(--brand); color: #1a0f08; padding: 13px 18px; width: 100%; margin-top: 18px; }
2323
.primary:disabled { opacity: .5; cursor: not-allowed; }
2424
.ghost { background: transparent; color: var(--ink); border: 1px solid var(--line); padding: 9px 14px; }
25+
.ghost:disabled { opacity: .5; cursor: not-allowed; }
2526
.row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
2627
.pill { font-family: ui-monospace, monospace; font-size: 12px; color: var(--muted); }
28+
.triple { display: grid; gap: 6px; }
29+
.triple .hint { font-size: 11px; color: var(--muted); margin-top: 8px; }
30+
.triple .hint a { color: var(--brand); }
2731
.fee { margin-top: 14px; padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px; font-size: 13px; }
2832
.fee b { color: var(--brand); }
2933
#log { margin-top: 18px; font-family: ui-monospace, monospace; font-size: 12px; color: var(--muted);
@@ -35,8 +39,8 @@
3539
<body>
3640
<div class="card">
3741
<div class="kicker">Useless dApp</div>
38-
<h1>Do a pointless thing 🪄</h1>
39-
<p class="sub">and route a fee to an affiliate through the Intuition FeeProxy. This is all the integration takes.</p>
42+
<h1>Make a pointless statement</h1>
43+
<p class="sub">Create a triple from three existing atoms, and route a fee to an affiliate through the Intuition FeeProxy. No IPFS pinning needed — triples only reference atoms that already exist. This is all the integration takes.</p>
4044

4145
<div class="row">
4246
<span class="pill" id="acct">Not connected</span>
@@ -46,20 +50,31 @@ <h1>Do a pointless thing 🪄</h1>
4650
<label>Affiliate address (the one you registered)</label>
4751
<input id="affiliate" value="0xE596096F4176b682E300d73963e7B04B383C1AA1" />
4852

53+
<div class="triple">
54+
<div class="row">
55+
<label style="margin:14px 0 0">Triple — three existing atom IDs (subject · predicate · object)</label>
56+
<button class="ghost" id="load" style="margin-top:14px">Load 3 from chain</button>
57+
</div>
58+
<input id="subject" placeholder="subject atom ID (0x… 32 bytes)" />
59+
<input id="predicate" placeholder="predicate atom ID (0x… 32 bytes)" />
60+
<input id="object" placeholder="object atom ID (0x… 32 bytes)" />
61+
<div class="hint">These must be atom IDs that already exist on Intuition testnet. Use “Load 3 from chain”, or paste IDs you find on the <a href="https://portal.intuition.systems/explore/atom" target="_blank" rel="noopener">portal explorer</a>.</div>
62+
</div>
63+
4964
<label>Amount (TRUST)</label>
5065
<input id="gross" value="0.5" />
5166

5267
<div class="fee" id="fee">Fee preview: connect + fill above…</div>
5368

54-
<button class="primary" id="go" disabled>Create an atom via this affiliate</button>
69+
<button class="primary" id="go" disabled>Create a triple via this affiliate</button>
5570

5671
<div id="log"></div>
5772
</div>
5873

5974
<script type="module">
6075
import {
6176
createPublicClient, createWalletClient, custom, http,
62-
parseAbi, parseEther, formatEther, toHex, isAddress,
77+
parseAbi, parseEther, formatEther, isAddress, isHex,
6378
} from 'https://esm.sh/viem@2'
6479

6580
const FEEPROXY = '0x15458457aC9009BB83137025E5D228A0783207d7'
@@ -74,13 +89,14 @@ <h1>Do a pointless thing 🪄</h1>
7489
}
7590

7691
const feeProxyAbi = parseAbi([
77-
'function createAtomsVia(address affiliate, bytes[] atomDatas, uint256[] assets, (uint256 maxFeeBps, uint256 maxFixedFee) feeGuard) payable returns (bytes32[])',
92+
'function createTriplesVia(address affiliate, bytes32[] subjectIds, bytes32[] predicateIds, bytes32[] objectIds, uint256[] assets, (uint256 maxFeeBps, uint256 maxFixedFee) feeGuard) payable returns (bytes32[])',
7893
'function previewCreationFee(address affiliate, uint256 grossAssets) view returns (uint256 fee, uint256 forwarded)',
7994
'function isAffiliateActive(address affiliate) view returns (bool)',
8095
])
8196
const multiVaultAbi = parseAbi([
8297
'function isApprovedToCreate(address sender, address creator) view returns (bool)',
8398
'function approve(address sender, uint8 approvalType)',
99+
'event AtomCreated(address indexed creator, bytes32 indexed termId, bytes atomData, address atomWallet)',
84100
])
85101

86102
const publicClient = createPublicClient({ chain, transport: http() })
@@ -89,12 +105,12 @@ <h1>Do a pointless thing 🪄</h1>
89105
const $ = (id) => document.getElementById(id)
90106
const log = (msg, cls = '') => { $('log').insertAdjacentHTML('beforeend', `<div class="${cls}">${msg}</div>`); $('log').scrollTop = 1e9 }
91107
const short = (a) => a.slice(0, 6) + '…' + a.slice(-4)
108+
const isId = (v) => isHex(v) && v.length === 66
92109

93110
async function connect() {
94111
if (!window.ethereum) return log('No wallet found (install MetaMask).', 'err')
95112
walletClient = createWalletClient({ chain, transport: custom(window.ethereum) })
96113
;[account] = await walletClient.requestAddresses()
97-
// ensure correct chain
98114
try { await walletClient.switchChain({ id: chain.id }) }
99115
catch { try { await walletClient.addChain({ chain }) } catch {} }
100116
$('acct').textContent = short(account) + ' · Intuition testnet'
@@ -103,49 +119,76 @@ <h1>Do a pointless thing 🪄</h1>
103119
refreshFee()
104120
}
105121

122+
// Read recent AtomCreated logs straight from the chain — no indexer — and
123+
// prefill the three fields with distinct existing atom IDs. A triple over
124+
// arbitrary recent atoms is meaningless on purpose (it is a useless dApp),
125+
// but every component is a real, already-pinned atom, so it always works.
126+
async function loadAtoms() {
127+
$('load').disabled = true
128+
try {
129+
log('Reading recent AtomCreated events…')
130+
const latest = await publicClient.getBlockNumber()
131+
const fromBlock = latest > 200000n ? latest - 200000n : 0n
132+
const logs = await publicClient.getLogs({
133+
address: MULTIVAULT,
134+
event: multiVaultAbi.find((x) => x.type === 'event' && x.name === 'AtomCreated'),
135+
fromBlock, toBlock: 'latest',
136+
})
137+
const ids = [...new Set(logs.map((l) => l.args.termId))].slice(-3)
138+
if (ids.length < 3) return log('Not enough recent atoms found — paste IDs from the portal instead.', 'err')
139+
$('subject').value = ids[0]; $('predicate').value = ids[1]; $('object').value = ids[2]
140+
log(`Loaded ${short(ids[0])} · ${short(ids[1])} · ${short(ids[2])}`, 'ok')
141+
} catch (e) {
142+
log('Could not read atoms: ' + (e.shortMessage || e.message) + ' — paste IDs from the portal instead.', 'err')
143+
} finally { $('load').disabled = false }
144+
}
145+
106146
async function refreshFee() {
107147
const affiliate = $('affiliate').value.trim()
108148
if (!isAddress(affiliate)) return $('fee').textContent = 'Enter a valid affiliate address.'
109149
let gross; try { gross = parseEther($('gross').value.trim() || '0') } catch { return }
110150
try {
111151
const active = await publicClient.readContract({ address: FEEPROXY, abi: feeProxyAbi, functionName: 'isAffiliateActive', args: [affiliate] })
112-
if (!active) return $('fee').innerHTML = '⚠️ This address is not a registered/active affiliate.'
152+
if (!active) return $('fee').textContent = 'This address is not a registered/active affiliate.'
113153
const [fee, forwarded] = await publicClient.readContract({ address: FEEPROXY, abi: feeProxyAbi, functionName: 'previewCreationFee', args: [affiliate, gross] })
114154
$('fee').innerHTML = `On ${formatEther(gross)} TRUST → affiliate fee <b>${formatEther(fee)} TRUST</b>, forwarded ${formatEther(forwarded)} TRUST.`
115155
} catch (e) { $('fee').textContent = 'Could not read fee: ' + (e.shortMessage || e.message) }
116156
}
117157

118158
async function go() {
119159
const affiliate = $('affiliate').value.trim()
160+
const subject = $('subject').value.trim(), predicate = $('predicate').value.trim(), object = $('object').value.trim()
120161
let gross; try { gross = parseEther($('gross').value.trim()) } catch { return log('Bad amount', 'err') }
121162
if (!isAddress(affiliate)) return log('Bad affiliate address', 'err')
163+
if (![subject, predicate, object].every(isId)) return log('Each atom ID must be a 32-byte hex value (0x + 64 chars).', 'err')
122164
$('go').disabled = true
123165
try {
124-
// 1) one-time MultiVault approval so the FeeProxy can create on your behalf
166+
// 1) one-time MultiVault approval so the FeeProxy can create on your
167+
// behalf — CREATION approval covers both atoms and triples.
125168
const approved = await publicClient.readContract({ address: MULTIVAULT, abi: multiVaultAbi, functionName: 'isApprovedToCreate', args: [FEEPROXY, account] })
126169
if (!approved) {
127170
log('Approving FeeProxy for CREATION on the MultiVault…')
128171
const h = await walletClient.writeContract({ account, address: MULTIVAULT, abi: multiVaultAbi, functionName: 'approve', args: [FEEPROXY, 4] })
129172
await publicClient.waitForTransactionReceipt({ hash: h })
130173
log(`approved · <a href="${EXPLORER}/tx/${h}" target="_blank">tx</a>`, 'ok')
131-
} else log('Already approved')
174+
} else log('Already approved')
132175

133-
// 2) route the creation through the affiliate
134-
log('Routing createAtomsVia through ' + short(affiliate) + '…')
135-
const data = toHex(`useless-dapp-atom-${Date.now()}`)
176+
// 2) route the triple creation through the affiliate
177+
log('Routing createTriplesVia through ' + short(affiliate) + '…')
136178
const guard = { maxFeeBps: 10000n, maxFixedFee: parseEther('1') }
137179
const hash = await walletClient.writeContract({
138-
account, address: FEEPROXY, abi: feeProxyAbi, functionName: 'createAtomsVia',
139-
args: [affiliate, [data], [gross], guard], value: gross,
180+
account, address: FEEPROXY, abi: feeProxyAbi, functionName: 'createTriplesVia',
181+
args: [affiliate, [subject], [predicate], [object], [gross], guard], value: gross,
140182
})
141183
await publicClient.waitForTransactionReceipt({ hash })
142-
log(`🎉 done — fee routed to the affiliate · <a href="${EXPLORER}/tx/${hash}" target="_blank">tx</a>`, 'ok')
184+
log(`done — triple created, fee routed to the affiliate · <a href="${EXPLORER}/tx/${hash}" target="_blank">tx</a>`, 'ok')
143185
} catch (e) {
144186
log('Failed: ' + (e.shortMessage || e.message), 'err')
145187
} finally { $('go').disabled = false }
146188
}
147189

148190
$('connect').onclick = connect
191+
$('load').onclick = loadAtoms
149192
$('go').onclick = go
150193
$('affiliate').oninput = refreshFee
151194
$('gross').oninput = refreshFee

0 commit comments

Comments
 (0)