mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-11-04 12:40:40 +00:00
Make premium claim windows manual close by default
This commit is contained in:
1 parent
e0e7755cf5
commit
37d871431b
5 files changed
+83
-33
No files matched your search
+23
-13
@@ -359,8 +359,8 @@ Premium 2.0 changes the post-payment path:
|
||||
|
||||
1. Artstr fetches and verifies the signed admin premium policy event.
|
||||
2. Creator publishes a premium event as usual. Artstr automatically stamps
|
||||
policy fields such as `softgate-epoch`, `claim-epoch`, `claim-until`,
|
||||
and `post-purchase-action`.
|
||||
policy fields such as `softgate-epoch`, `claim-epoch`, optional
|
||||
`claim-until`, and `post-purchase-action`.
|
||||
3. Buyer unlocks and pays split zaps as today.
|
||||
4. Artstr verifies all required receipts.
|
||||
5. Browser decrypts the public premium payload once.
|
||||
@@ -410,9 +410,10 @@ Content:
|
||||
"v": 1,
|
||||
"activeSoftgateEpoch": "2026-07",
|
||||
"activeClaimEpoch": "2026-07",
|
||||
"defaultClaimDays": 90,
|
||||
"minClaimDays": 7,
|
||||
"maxClaimDays": 180,
|
||||
"defaultClaimDays": 0,
|
||||
"claimWindow": "manual",
|
||||
"minClaimDays": 0,
|
||||
"maxClaimDays": 3650,
|
||||
"privateCopies": "required",
|
||||
"firstUnlockDefault": "allowed",
|
||||
"issuedAt": 1783200000,
|
||||
@@ -477,7 +478,7 @@ Artstr-controlled automatic fields:
|
||||
["premium-mode", "softgate-v1.5"],
|
||||
["softgate-epoch", "<policy.activeSoftgateEpoch>"],
|
||||
["claim-epoch", "<policy.activeClaimEpoch>"],
|
||||
["claim-until", "<now + policy.defaultClaimDays>"],
|
||||
["claim-until", "<now + policy.defaultClaimDays, omitted when manual>"],
|
||||
["claim-policy", "private-snapshot-required"],
|
||||
["post-purchase-action", "private-snapshot-required"]
|
||||
]
|
||||
@@ -494,7 +495,7 @@ Envelope additions:
|
||||
"claimEpoch": "2026-07",
|
||||
"publicDesignSalt": "<base64-random-32-bytes>",
|
||||
"purchaseScope": "address",
|
||||
"claimUntil": 1796083200,
|
||||
"claimUntil": 0,
|
||||
"postPurchaseAction": "private-snapshot-required"
|
||||
},
|
||||
"license": {
|
||||
@@ -507,8 +508,8 @@ Envelope additions:
|
||||
Creator UI should stay simple:
|
||||
|
||||
```text
|
||||
Premium purchases are claimable for 90 days. Buyers keep a private
|
||||
encrypted copy after purchase.
|
||||
Premium purchases stay claimable until Artstr closes the epoch. Buyers keep
|
||||
a private encrypted copy after purchase.
|
||||
```
|
||||
|
||||
Do not expose `epoch`, `KDF`, or `policy manifest` language in normal
|
||||
@@ -522,9 +523,12 @@ Claim windows are honest-client policy.
|
||||
|
||||
Rules:
|
||||
|
||||
- `claim-until` controls first-time public-premium unlock attempts;
|
||||
- after `claim-until`, honest clients stop offering a new first-time
|
||||
unlock from that public event;
|
||||
- `claim-until` is optional. If it is absent or `0`, the public listing stays
|
||||
claimable until the admin manually closes the epoch;
|
||||
- when `claim-until` is present, it controls first-time public-premium
|
||||
unlock attempts;
|
||||
- after `claim-until`, honest clients stop offering a new first-time unlock
|
||||
from that public event;
|
||||
- already-claimed private purchase copies keep opening;
|
||||
- self-authored premium events can keep auto-unlocking for the creator;
|
||||
- expired source events remain browseable as previews;
|
||||
@@ -533,7 +537,7 @@ Rules:
|
||||
|
||||
Recommended defaults:
|
||||
|
||||
- default claim window: 90 days;
|
||||
- default claim window: unlimited/manual close (`defaultClaimDays: 0`);
|
||||
- creator does not configure it in v2.0;
|
||||
- admin can later expose creator choice within `minClaimDays` and
|
||||
`maxClaimDays` if there is real demand;
|
||||
@@ -542,6 +546,12 @@ Recommended defaults:
|
||||
|
||||
Claim windows should never be described as cryptographic expiry.
|
||||
|
||||
If an epoch is manually closed and a creator wants an old public listing to
|
||||
be purchasable again, the creator should update/republish the listing under
|
||||
the current active policy. The new public event gets the new active epoch and
|
||||
becomes a fresh storefront. Existing buyers keep their older private purchase
|
||||
copies as snapshots of what they bought.
|
||||
|
||||
---
|
||||
|
||||
## 9. Buyer-Private Purchase Copy
|
||||
|
||||
+13
-7
@@ -5633,11 +5633,11 @@
|
||||
</details>
|
||||
<details id="premiumPolicyAdminDetails" style="margin-top:12px;border-top:1px solid var(--line);padding-top:10px;display:none">
|
||||
<summary style="font-weight:700;font-size:.82rem;cursor:pointer;color:var(--muted)">Premium policy admin</summary>
|
||||
<p class="hint" style="margin:8px 0 6px">Publish the signed Premium 2.0 policy event used for epoch and claim-window stamping.</p>
|
||||
<p class="hint" style="margin:8px 0 6px">Publish the signed Premium 2.0 policy event used for epoch and claim-window stamping. Leave claim days blank/0 to keep listings claimable until you manually close the epoch.</p>
|
||||
<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px">
|
||||
<div><label for="premiumPolicySoftgateEpoch">Softgate epoch</label><input id="premiumPolicySoftgateEpoch" type="text" value="2026-07" /></div>
|
||||
<div><label for="premiumPolicyClaimEpoch">Claim epoch</label><input id="premiumPolicyClaimEpoch" type="text" value="2026-07" /></div>
|
||||
<div><label for="premiumPolicyDefaultDays">Claim days</label><input id="premiumPolicyDefaultDays" type="number" min="7" max="180" step="1" value="90" /></div>
|
||||
<div><label for="premiumPolicyDefaultDays">Claim days</label><input id="premiumPolicyDefaultDays" type="number" min="0" max="3650" step="1" value="0" placeholder="0 = manual close" /></div>
|
||||
<div><label for="premiumPolicyEpochStatus">Epoch status</label><select id="premiumPolicyEpochStatus"><option value="active">Active</option><option value="legacy">Legacy</option><option value="closed">Closed</option></select></div>
|
||||
</div>
|
||||
<div class="buttonRow" style="margin:8px 0 0">
|
||||
@@ -26300,15 +26300,20 @@
|
||||
function buildPremiumPolicyFromAdminForm() {
|
||||
const softgateEpoch = ($('premiumPolicySoftgateEpoch')?.value || '2026-07').trim();
|
||||
const claimEpoch = ($('premiumPolicyClaimEpoch')?.value || softgateEpoch).trim();
|
||||
const defaultClaimDays = Math.max(7, Math.min(180, Number($('premiumPolicyDefaultDays')?.value) || 90));
|
||||
const rawClaimDays = ($('premiumPolicyDefaultDays')?.value || '').trim();
|
||||
const parsedClaimDays = Number(rawClaimDays);
|
||||
const defaultClaimDays = rawClaimDays === '' || parsedClaimDays === 0
|
||||
? 0
|
||||
: Math.max(1, Math.min(3650, parsedClaimDays || 0));
|
||||
const status = ($('premiumPolicyEpochStatus')?.value || 'active').trim();
|
||||
return {
|
||||
v: 1,
|
||||
activeSoftgateEpoch: softgateEpoch,
|
||||
activeClaimEpoch: claimEpoch,
|
||||
defaultClaimDays,
|
||||
minClaimDays: 7,
|
||||
maxClaimDays: 180,
|
||||
claimWindow: defaultClaimDays === 0 ? 'manual' : 'fixed-days',
|
||||
minClaimDays: defaultClaimDays === 0 ? 0 : 1,
|
||||
maxClaimDays: 3650,
|
||||
privateCopies: 'required',
|
||||
firstUnlockDefault: 'allowed',
|
||||
issuedAt: Math.floor(Date.now() / 1000),
|
||||
@@ -26340,9 +26345,10 @@
|
||||
const policy = loaded.policy || {};
|
||||
if ($('premiumPolicySoftgateEpoch')) $('premiumPolicySoftgateEpoch').value = policy.activeSoftgateEpoch || '2026-07';
|
||||
if ($('premiumPolicyClaimEpoch')) $('premiumPolicyClaimEpoch').value = policy.activeClaimEpoch || policy.activeSoftgateEpoch || '2026-07';
|
||||
if ($('premiumPolicyDefaultDays')) $('premiumPolicyDefaultDays').value = String(policy.defaultClaimDays || 90);
|
||||
if ($('premiumPolicyDefaultDays')) $('premiumPolicyDefaultDays').value = Number(policy.defaultClaimDays || 0) > 0 ? String(policy.defaultClaimDays) : '0';
|
||||
if ($('premiumPolicyEpochStatus')) $('premiumPolicyEpochStatus').value = policy.epochs?.[policy.activeSoftgateEpoch]?.status || 'active';
|
||||
statusEl.textContent = `Active policy: ${policy.activeSoftgateEpoch || 'unknown'} / claim ${policy.activeClaimEpoch || 'unknown'}.`;
|
||||
const claimWindow = Number(policy.defaultClaimDays || 0) > 0 ? `${policy.defaultClaimDays} days` : 'manual close';
|
||||
statusEl.textContent = `Active policy: ${policy.activeSoftgateEpoch || 'unknown'} / claim ${policy.activeClaimEpoch || 'unknown'} / ${claimWindow}.`;
|
||||
} catch (err) {
|
||||
statusEl.textContent = err?.message || 'No valid premium policy found.';
|
||||
}
|
||||
|
||||
+15
-13
@@ -55,13 +55,22 @@ export function buildPremiumPublishStamp(policy, {
|
||||
if (supportedSoftgateEpochs.length && !supportedSoftgateEpochs.includes(softgateEpoch)) {
|
||||
throw new Error(`Premium policy epoch ${softgateEpoch} is not supported by this client.`);
|
||||
}
|
||||
const minDays = Math.max(1, Number(policy.minClaimDays) || 1);
|
||||
const maxDays = Math.max(minDays, Number(policy.maxClaimDays) || 180);
|
||||
const requestedDays = Number(policy.defaultClaimDays) || 90;
|
||||
const claimDays = Math.min(maxDays, Math.max(minDays, requestedDays));
|
||||
const claimUntil = Number(now) + (claimDays * 86400);
|
||||
const requestedDays = Number(policy.defaultClaimDays);
|
||||
const manualClaimClose = requestedDays === 0 || policy.claimWindow === 'manual';
|
||||
const minDays = manualClaimClose ? 0 : Math.max(1, Number(policy.minClaimDays) || 1);
|
||||
const maxDays = manualClaimClose ? 0 : Math.max(minDays, Number(policy.maxClaimDays) || 180);
|
||||
const claimDays = manualClaimClose ? 0 : Math.min(maxDays, Math.max(minDays, requestedDays || 90));
|
||||
const claimUntil = claimDays > 0 ? Number(now) + (claimDays * 86400) : 0;
|
||||
const premiumMode = PREMIUM_MODE_V15;
|
||||
const postPurchaseAction = PRIVATE_SNAPSHOT_ACTION;
|
||||
const tags = [
|
||||
['premium-mode', premiumMode],
|
||||
['softgate-epoch', softgateEpoch],
|
||||
['claim-epoch', claimEpoch],
|
||||
['claim-policy', postPurchaseAction],
|
||||
['post-purchase-action', postPurchaseAction],
|
||||
];
|
||||
if (claimUntil) tags.splice(3, 0, ['claim-until', String(claimUntil)]);
|
||||
return {
|
||||
premiumMode,
|
||||
softgateEpoch,
|
||||
@@ -70,14 +79,7 @@ export function buildPremiumPublishStamp(policy, {
|
||||
claimDays,
|
||||
postPurchaseAction,
|
||||
encryptedTag: epochConfig.softgateKdf || 'artstr-softgate-v1.5',
|
||||
tags: [
|
||||
['premium-mode', premiumMode],
|
||||
['softgate-epoch', softgateEpoch],
|
||||
['claim-epoch', claimEpoch],
|
||||
['claim-until', String(claimUntil)],
|
||||
['claim-policy', postPurchaseAction],
|
||||
['post-purchase-action', postPurchaseAction],
|
||||
],
|
||||
tags,
|
||||
envelopeSoftgate: {
|
||||
claimEpoch,
|
||||
claimUntil,
|
||||
|
||||
@@ -68,6 +68,35 @@ test('builds automatic premium publish stamps from admin policy', async () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('builds unlimited premium publish stamps for manual-close policies', async () => {
|
||||
const valid = await fixture('policy-valid-active.json');
|
||||
const { policy } = validatePremiumPolicyEvent(valid, { platformPubkey });
|
||||
const stamp = buildPremiumPublishStamp({
|
||||
...policy,
|
||||
defaultClaimDays: 0,
|
||||
claimWindow: 'manual',
|
||||
minClaimDays: 0,
|
||||
}, {
|
||||
now: 1783200000,
|
||||
supportedSoftgateEpochs: ['2026-05', '2026-07'],
|
||||
});
|
||||
|
||||
assert.equal(stamp.claimDays, 0);
|
||||
assert.equal(stamp.claimUntil, 0);
|
||||
assert.deepEqual(stamp.envelopeSoftgate, {
|
||||
claimEpoch: '2026-07',
|
||||
claimUntil: 0,
|
||||
postPurchaseAction: 'private-snapshot-required',
|
||||
});
|
||||
assert.deepEqual(stamp.tags, [
|
||||
['premium-mode', 'softgate-v1.5'],
|
||||
['softgate-epoch', '2026-07'],
|
||||
['claim-epoch', '2026-07'],
|
||||
['claim-policy', 'private-snapshot-required'],
|
||||
['post-purchase-action', 'private-snapshot-required'],
|
||||
]);
|
||||
});
|
||||
|
||||
test('rejects premium publish stamps for unsupported or closed policy epochs', async () => {
|
||||
const valid = await fixture('policy-valid-active.json');
|
||||
const { policy } = validatePremiumPolicyEvent(valid, { platformPubkey });
|
||||
|
||||
@@ -52,6 +52,9 @@ test('index page exposes admin-only premium policy publishing controls', async (
|
||||
assert.match(html, /async function publishPremiumPolicyFromSettings\(\)/);
|
||||
assert.match(html, /helpers\.buildPremiumPolicyEventDraft\(policy\)/);
|
||||
assert.match(html, /helpers\.validatePremiumPolicyEvent\(signed, \{ platformPubkey: platform\.getPubkeyHex\(\) \}\)/);
|
||||
assert.match(html, /placeholder="0 = manual close"/);
|
||||
assert.match(html, /claimWindow: defaultClaimDays === 0 \? 'manual' : 'fixed-days'/);
|
||||
assert.match(html, /statusEl\.textContent = `Active policy: \$\{policy\.activeSoftgateEpoch \|\| 'unknown'\} \/ claim \$\{policy\.activeClaimEpoch \|\| 'unknown'\} \/ \$\{claimWindow\}\.`/);
|
||||
assert.match(html, /on\('premiumPolicyPublishBtn', 'click'/);
|
||||
assert.match(html, /Premium policy published/);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user