Search Apps Documentation Source Content File Folder Download Copy Actions Download

access.gno

3.70 Kb · 127 lines
  1// GnoPulse access: on-chain subscriptions + prepaid credits that gate the GnoPulse API.
  2// Users pay from their own wallet; the realm holds only what they pay it.
  3package access
  4
  5import (
  6	"chain"
  7	"chain/banker"
  8	"chain/runtime"
  9	"chain/runtime/unsafe"
 10	"strconv"
 11	"time"
 12)
 13
 14var (
 15	owner = address("g1jsl3x46uhk75lvws0f3mjnc49g84m2hu4rpuyt")
 16
 17	priceUgnot int64 = 5_000_000
 18	periodSec  int64 = 30 * 24 * 60 * 60
 19	creditRate int64 = 1
 20
 21	subs    = map[address]int64{} // addr -> expiry (unix)
 22	credits = map[address]int64{} // addr -> credit balance
 23)
 24
 25// Subscribe buys pro time for the caller; each price unit sent buys one period.
 26func Subscribe(cur realm) {
 27	runtime.AssertOriginCall()
 28	payer := cur.Previous().Address()
 29	paid := int64(unsafe.OriginSend().AmountOf("ugnot"))
 30	if paid < priceUgnot {
 31		panic("payment below price")
 32	}
 33	base := subs[payer]
 34	if now := time.Now().Unix(); base < now {
 35		base = now
 36	}
 37	subs[payer] = base + (paid/priceUgnot)*periodSec
 38	chain.Emit("Subscribe", "addr", payer.String(), "expiresAt", strconv.FormatInt(subs[payer], 10))
 39}
 40
 41// Login does nothing. Sign-In-With-Gno signs (never broadcasts) a call to it so the wallet shows
 42// a "Login" to GnoPulse rather than a token transfer; the challenge nonce rides in the tx memo.
 43func Login(cur realm) {}
 44
 45// TopUp buys prepaid credits for the caller.
 46func TopUp(cur realm) {
 47	runtime.AssertOriginCall()
 48	payer := cur.Previous().Address()
 49	paid := int64(unsafe.OriginSend().AmountOf("ugnot"))
 50	if paid <= 0 {
 51		panic("no payment")
 52	}
 53	credits[payer] += paid * creditRate
 54	chain.Emit("TopUp", "addr", payer.String(), "credits", strconv.FormatInt(credits[payer], 10))
 55}
 56
 57func HasAccess(addr address) bool { return subs[addr] > time.Now().Unix() || credits[addr] > 0 }
 58
 59func TierOf(addr address) string {
 60	if subs[addr] > time.Now().Unix() {
 61		return "pro"
 62	}
 63	return "free"
 64}
 65
 66func CreditsOf(addr address) int64  { return credits[addr] }
 67func ExpiresAt(addr address) int64  { return subs[addr] }
 68func Owner() address                { return owner }
 69func Config() (int64, int64, int64) { return priceUgnot, periodSec, creditRate }
 70
 71// Consume debits credits (owner settles metered usage in batches). Floors at zero.
 72func Consume(cur realm, addr address, n int64) {
 73	assertOwner(cur)
 74	if n < 0 {
 75		panic("negative")
 76	}
 77	if credits[addr] <= n {
 78		delete(credits, addr)
 79	} else {
 80		credits[addr] -= n
 81	}
 82}
 83
 84func SetConfig(cur realm, price, period, rate int64) {
 85	assertOwner(cur)
 86	if price <= 0 || period <= 0 || rate <= 0 {
 87		panic("must be positive")
 88	}
 89	priceUgnot, periodSec, creditRate = price, period, rate
 90}
 91
 92func Withdraw(cur realm, amount int64) {
 93	assertOwner(cur)
 94	if amount <= 0 {
 95		panic("must be positive")
 96	}
 97	banker.NewBanker(banker.BankerTypeRealmSend, cur).SendCoins(
 98		cur.Address(), owner, chain.Coins{{"ugnot", amount}})
 99}
100
101func TransferOwnership(cur realm, newOwner address) {
102	assertOwner(cur)
103	if newOwner == "" {
104		panic("empty address")
105	}
106	owner = newOwner
107}
108
109// assertOwner requires a DIRECT call from the owner. cur.Previous() is the immediate caller, so a
110// relaying realm (owner -> X -> here) would fail this — no separate origin assert needed.
111func assertOwner(cur realm) {
112	if cur.Previous().Address() != owner {
113		panic("owner only")
114	}
115}
116
117func Render(path string) string {
118	if len(path) > 5 && path[:5] == "addr/" {
119		a := address(path[5:])
120		return "# " + a.String() + "\n\n- tier: " + TierOf(a) +
121			"\n- expires: " + strconv.FormatInt(subs[a], 10) +
122			"\n- credits: " + strconv.FormatInt(credits[a], 10) + "\n"
123	}
124	return "# GnoPulse Access\n\nSubscribe() or TopUp() from your wallet.\n\n- price: " +
125		strconv.FormatInt(priceUgnot, 10) + " ugnot / " + strconv.FormatInt(periodSec, 10) + "s\n" +
126		"- credits: " + strconv.FormatInt(creditRate, 10) + " per ugnot\n"
127}