@akita-protocol/algomd-core 1.0.0-alpha.0 → 1.0.0-alpha.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/dist/{chunk-NRZTLFVI.js → chunk-6KYOB5JM.js} +16 -1
- package/dist/chunk-6KYOB5JM.js.map +1 -0
- package/dist/{families-CiQWc-YP.d.cts → families-R1GgJpSO.d.cts} +1156 -1
- package/dist/{families-CiQWc-YP.d.ts → families-R1GgJpSO.d.ts} +1156 -1
- package/dist/index.cjs +1851 -221
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1612 -5
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +18 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +4 -2
- package/dist/react.d.ts +4 -2
- package/dist/react.js +3 -1
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/src/contracts/schemas.generated.ts +1422 -0
- package/src/guild.ts +260 -0
- package/src/index.ts +14 -0
- package/src/kinds.ts +15 -0
- package/src/react/index.ts +3 -0
- package/src/sources/algod.ts +7 -1
- package/dist/chunk-NRZTLFVI.js.map +0 -1
package/src/guild.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import algosdk from 'algosdk'
|
|
2
|
+
import { fromHex } from './chain/bytes'
|
|
3
|
+
import type { TealKeyValue } from './chain/indexer-types'
|
|
4
|
+
import { stateFromTeal } from './chain/state'
|
|
5
|
+
import { boxName, decodeBoxValue, decodeGlobalState, type Decoded } from './contracts/decode'
|
|
6
|
+
import { requestJson, type FetchLike } from './http'
|
|
7
|
+
import { readBox } from './akita'
|
|
8
|
+
import type { Endpoint } from './types'
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* Guild proposals. A proposal lives in the Guild app's `proposals` box; once
|
|
12
|
+
* voting opens, its ballot lives in the paired consensus app's `ballots` box.
|
|
13
|
+
* The ref id is `<guildAppId>/<proposalId>`, since proposal ids count per Guild.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type GuildProposalStatus = 'draft' | 'invalid' | 'voting' | 'rejected' | 'approved' | 'executed'
|
|
17
|
+
|
|
18
|
+
const STATUS: Record<string, GuildProposalStatus> = {
|
|
19
|
+
'0': 'draft',
|
|
20
|
+
'10': 'invalid',
|
|
21
|
+
'20': 'voting',
|
|
22
|
+
'30': 'rejected',
|
|
23
|
+
'40': 'approved',
|
|
24
|
+
'50': 'executed',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface GuildProposalAction {
|
|
28
|
+
/** Guild action type (akita-sc guilds/core/constants.ts). */
|
|
29
|
+
type: number
|
|
30
|
+
label: string
|
|
31
|
+
/** Plugin application, for plugin actions. */
|
|
32
|
+
pluginAppId?: string
|
|
33
|
+
/** Delegated caller, for add and remove plugin actions. The zero address means members by vote. */
|
|
34
|
+
caller?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface GuildBallotData {
|
|
38
|
+
resolved: boolean
|
|
39
|
+
approved: boolean
|
|
40
|
+
/** Unix seconds. */
|
|
41
|
+
votingEnd: string
|
|
42
|
+
eligiblePower: string
|
|
43
|
+
voteCount: string
|
|
44
|
+
/** Fixed point, 100000 = 100%. */
|
|
45
|
+
requirements: { participation: string; approval: string; minimumApprovals: string }
|
|
46
|
+
votes: { approvals: string; rejections: string; abstains: string }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface GuildProposalData {
|
|
50
|
+
guildAppId: string
|
|
51
|
+
proposalId: string
|
|
52
|
+
consensusAppId?: string
|
|
53
|
+
walletAppId?: string
|
|
54
|
+
status: GuildProposalStatus
|
|
55
|
+
creator?: string
|
|
56
|
+
/** Unix seconds. */
|
|
57
|
+
created: string
|
|
58
|
+
votingTs: string
|
|
59
|
+
actions: GuildProposalAction[]
|
|
60
|
+
ballot?: GuildBallotData
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const GUILD_ZERO_ADDRESS = algosdk.ALGORAND_ZERO_ADDRESS_STRING
|
|
64
|
+
|
|
65
|
+
/** Splits a `guild-proposal` id into its Guild app id and proposal id. */
|
|
66
|
+
export function splitGuildProposalId(id: string): { guildAppId: string; proposalId: string } | undefined {
|
|
67
|
+
const match = /^(\d+)\/(\d+)$/.exec(id.trim())
|
|
68
|
+
return match ? { guildAppId: match[1]!, proposalId: match[2]! } : undefined
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------- actions
|
|
72
|
+
|
|
73
|
+
const ACTION_LABELS: Record<number, string> = {
|
|
74
|
+
10: 'Upgrade the guild',
|
|
75
|
+
20: 'Install a plugin',
|
|
76
|
+
21: 'Install a named plugin',
|
|
77
|
+
22: 'Change plugin vote settings',
|
|
78
|
+
30: 'Run a plugin',
|
|
79
|
+
31: 'Revoke a plugin run',
|
|
80
|
+
40: 'Remove a plugin',
|
|
81
|
+
41: 'Remove a named plugin',
|
|
82
|
+
50: 'Add spending allowances',
|
|
83
|
+
60: 'Remove spending allowances',
|
|
84
|
+
70: 'Create an escrow',
|
|
85
|
+
71: 'Lock or unlock an escrow',
|
|
86
|
+
72: 'Reclaim from an escrow',
|
|
87
|
+
80: 'Change proposal rules',
|
|
88
|
+
81: 'Change the action limit',
|
|
89
|
+
82: 'Create a wallet',
|
|
90
|
+
83: 'Change membership',
|
|
91
|
+
84: 'Change membership vote settings',
|
|
92
|
+
86: 'Set the chat steward',
|
|
93
|
+
90: 'Update the wallet',
|
|
94
|
+
91: 'Hand the wallet to a new admin',
|
|
95
|
+
100: 'Finish setup',
|
|
96
|
+
110: 'Lock the plugin set',
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readUint64(data: Uint8Array, offset: number): bigint | undefined {
|
|
100
|
+
if (data.length < offset + 8) return undefined
|
|
101
|
+
let value = 0n
|
|
102
|
+
for (let i = 0; i < 8; i += 1) value = (value << 8n) | BigInt(data[offset + i]!)
|
|
103
|
+
return value
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Labels a proposal's on-chain actions. Plugin actions decode their static head
|
|
108
|
+
* `(uint64 plugin, address caller, ...)` so a view can say which agent a plugin is for.
|
|
109
|
+
*/
|
|
110
|
+
export function summarizeGuildActions(actions: ReadonlyArray<readonly [number, Uint8Array]>): GuildProposalAction[] {
|
|
111
|
+
return actions.map(([type, data]) => {
|
|
112
|
+
const label = ACTION_LABELS[type] ?? `Action ${type}`
|
|
113
|
+
if (type !== 20 && type !== 22 && type !== 30 && type !== 40) return { type, label }
|
|
114
|
+
const plugin = readUint64(data, 0)
|
|
115
|
+
const action: GuildProposalAction = { type, label, ...(plugin !== undefined && { pluginAppId: plugin.toString() }) }
|
|
116
|
+
if ((type === 20 || type === 40) && data.length >= 40) action.caller = algosdk.encodeAddress(data.slice(8, 40))
|
|
117
|
+
return action
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------- chain read
|
|
122
|
+
|
|
123
|
+
const str = (v: Decoded | undefined): string => (typeof v === 'string' ? v : v == null ? '' : String(v))
|
|
124
|
+
const field = (v: Decoded | undefined, k: string): Decoded | undefined =>
|
|
125
|
+
v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, Decoded>)[k] : undefined
|
|
126
|
+
const positive = (v: Decoded | undefined) => (str(v) && str(v) !== '0' ? str(v) : undefined)
|
|
127
|
+
|
|
128
|
+
function decodedActions(value: Decoded | undefined): Array<[number, Uint8Array]> {
|
|
129
|
+
if (!Array.isArray(value)) return []
|
|
130
|
+
return value.flatMap((entry) => {
|
|
131
|
+
if (!Array.isArray(entry) || entry.length < 2) return []
|
|
132
|
+
const type = Number(str(entry[0]))
|
|
133
|
+
const raw = entry[1]
|
|
134
|
+
const data =
|
|
135
|
+
typeof raw === 'string' ? fromHex(raw) : Array.isArray(raw) ? Uint8Array.from(raw, (b) => Number(str(b))) : new Uint8Array()
|
|
136
|
+
return Number.isSafeInteger(type) ? [[type, data] as [number, Uint8Array]] : []
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function ballotFrom(b: Decoded): GuildBallotData {
|
|
141
|
+
const req = field(b, 'requirements')
|
|
142
|
+
const votes = field(b, 'votes')
|
|
143
|
+
return {
|
|
144
|
+
resolved: field(b, 'resolved') === true,
|
|
145
|
+
approved: field(b, 'approved') === true,
|
|
146
|
+
votingEnd: str(field(b, 'votingEnd')),
|
|
147
|
+
eligiblePower: str(field(b, 'eligiblePower')),
|
|
148
|
+
voteCount: str(field(b, 'voteCount')),
|
|
149
|
+
requirements: {
|
|
150
|
+
participation: str(field(req, 'participation')) || '0',
|
|
151
|
+
approval: str(field(req, 'approval')) || '0',
|
|
152
|
+
minimumApprovals: str(field(req, 'minimumApprovals')) || '0',
|
|
153
|
+
},
|
|
154
|
+
votes: {
|
|
155
|
+
approvals: str(field(votes, 'approvals')) || '0',
|
|
156
|
+
rejections: str(field(votes, 'rejections')) || '0',
|
|
157
|
+
abstains: str(field(votes, 'abstains')) || '0',
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Reads a proposal from its Guild's boxes, plus its ballot once voting has opened. */
|
|
163
|
+
export async function readGuildProposal(fetchFn: FetchLike, algod: Endpoint, id: string): Promise<GuildProposalData | undefined> {
|
|
164
|
+
const parts = splitGuildProposalId(id)
|
|
165
|
+
if (!parts) return undefined
|
|
166
|
+
const { guildAppId, proposalId } = parts
|
|
167
|
+
const [app, value] = await Promise.all([
|
|
168
|
+
requestJson<{ params: { 'global-state'?: TealKeyValue[] } }>(fetchFn, algod, `/v2/applications/${guildAppId}`),
|
|
169
|
+
readBox(fetchFn, algod, guildAppId, boxName('guild', 'proposals', BigInt(proposalId))),
|
|
170
|
+
])
|
|
171
|
+
if (!app || !value) return undefined
|
|
172
|
+
const guild = decodeGlobalState('guild', stateFromTeal(app.params['global-state']))
|
|
173
|
+
const p = decodeBoxValue('guild', 'proposals', value)
|
|
174
|
+
const status = STATUS[str(field(p, 'status'))] ?? 'invalid'
|
|
175
|
+
const consensusAppId = positive(guild.consensus)
|
|
176
|
+
const creator = str(field(p, 'creator'))
|
|
177
|
+
const data: GuildProposalData = {
|
|
178
|
+
guildAppId,
|
|
179
|
+
proposalId,
|
|
180
|
+
consensusAppId,
|
|
181
|
+
walletAppId: positive(guild.wallet),
|
|
182
|
+
status,
|
|
183
|
+
creator: creator && creator !== GUILD_ZERO_ADDRESS ? creator : undefined,
|
|
184
|
+
created: str(field(p, 'created')),
|
|
185
|
+
votingTs: str(field(p, 'votingTs')),
|
|
186
|
+
actions: summarizeGuildActions(decodedActions(field(p, 'actions'))),
|
|
187
|
+
}
|
|
188
|
+
if (consensusAppId && status !== 'draft' && status !== 'invalid') {
|
|
189
|
+
const ballot = await readBox(fetchFn, algod, consensusAppId, boxName('guildConsensus', 'ballots', BigInt(proposalId))).catch(() => undefined)
|
|
190
|
+
if (ballot) data.ballot = ballotFrom(decodeBoxValue('guildConsensus', 'ballots', ballot))
|
|
191
|
+
}
|
|
192
|
+
return data
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------- outcome
|
|
196
|
+
|
|
197
|
+
const HUNDRED = 100_000n
|
|
198
|
+
|
|
199
|
+
function meets(b: GuildBallotData, approvals: bigint, rejections: bigint, turnout: bigint): boolean {
|
|
200
|
+
const participation = BigInt(b.requirements.participation)
|
|
201
|
+
const approval = BigInt(b.requirements.approval)
|
|
202
|
+
const minimum = BigInt(b.requirements.minimumApprovals)
|
|
203
|
+
const eligible = BigInt(b.eligiblePower || '0')
|
|
204
|
+
const decisive = approvals + rejections
|
|
205
|
+
return (
|
|
206
|
+
approvals > 0n &&
|
|
207
|
+
(participation === 0n || turnout * HUNDRED >= eligible * participation) &&
|
|
208
|
+
(minimum === 0n || approvals >= minimum) &&
|
|
209
|
+
(approval === 0n || (decisive > 0n && approvals * HUNDRED >= decisive * approval))
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* How an open ballot resolves if it is closed now (`now` in Unix seconds),
|
|
215
|
+
* mirroring the consensus contract: after `votingEnd` by the votes cast; before
|
|
216
|
+
* it only once the members who have not voted cannot change the result.
|
|
217
|
+
* `undefined` while the result is still open.
|
|
218
|
+
*/
|
|
219
|
+
export function guildBallotOutcome(b: GuildBallotData, now: number): 'approved' | 'rejected' | undefined {
|
|
220
|
+
if (b.resolved) return undefined
|
|
221
|
+
const approvals = BigInt(b.votes.approvals)
|
|
222
|
+
const rejections = BigInt(b.votes.rejections)
|
|
223
|
+
const turnout = approvals + rejections + BigInt(b.votes.abstains)
|
|
224
|
+
const passes = meets(b, approvals, rejections, turnout)
|
|
225
|
+
if (now > Number(b.votingEnd)) return passes ? 'approved' : 'rejected'
|
|
226
|
+
const eligible = BigInt(b.eligiblePower || '0')
|
|
227
|
+
if (eligible === 0n) return undefined
|
|
228
|
+
const remaining = eligible > turnout ? eligible - turnout : 0n
|
|
229
|
+
if (passes) return meets(b, approvals, rejections + remaining, turnout) ? 'approved' : undefined
|
|
230
|
+
return meets(b, approvals + remaining, rejections, turnout + remaining) ? undefined : 'rejected'
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** A fixed point percentage (100000 = 100%), rounded up so a requirement never reads lower than it is. */
|
|
234
|
+
export function formatGuildPercent(value: string): string {
|
|
235
|
+
const tenths = Number((BigInt(value || '0') * 1_000n + HUNDRED - 1n) / HUNDRED)
|
|
236
|
+
return `${tenths % 10 === 0 ? tenths / 10 : (tenths / 10).toFixed(1)}%`
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function guildProposalStatusLabel(status: GuildProposalStatus): string {
|
|
240
|
+
switch (status) {
|
|
241
|
+
case 'draft': return 'Draft'
|
|
242
|
+
case 'invalid': return 'Invalid'
|
|
243
|
+
case 'voting': return 'Voting'
|
|
244
|
+
case 'rejected': return 'Rejected'
|
|
245
|
+
case 'approved': return 'Passed'
|
|
246
|
+
case 'executed': return 'Done'
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** The ballot's requirements in a few words, e.g. "Needs 50% yes, at least 1 approval". */
|
|
251
|
+
export function guildRequirementsText(b: GuildBallotData): string {
|
|
252
|
+
const { approval, minimumApprovals, participation } = b.requirements
|
|
253
|
+
return [
|
|
254
|
+
approval !== '0' ? `Needs ${formatGuildPercent(approval)} yes` : null,
|
|
255
|
+
minimumApprovals !== '0' ? `at least ${minimumApprovals} approval${minimumApprovals === '1' ? '' : 's'}` : null,
|
|
256
|
+
participation !== '0' ? `${formatGuildPercent(participation)} turnout` : null,
|
|
257
|
+
]
|
|
258
|
+
.filter(Boolean)
|
|
259
|
+
.join(', ')
|
|
260
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -78,6 +78,20 @@ export {
|
|
|
78
78
|
type DualStakeData,
|
|
79
79
|
type PrivateContentData,
|
|
80
80
|
} from './akita'
|
|
81
|
+
export {
|
|
82
|
+
GUILD_ZERO_ADDRESS,
|
|
83
|
+
formatGuildPercent,
|
|
84
|
+
guildBallotOutcome,
|
|
85
|
+
guildProposalStatusLabel,
|
|
86
|
+
guildRequirementsText,
|
|
87
|
+
readGuildProposal,
|
|
88
|
+
splitGuildProposalId,
|
|
89
|
+
summarizeGuildActions,
|
|
90
|
+
type GuildBallotData,
|
|
91
|
+
type GuildProposalAction,
|
|
92
|
+
type GuildProposalData,
|
|
93
|
+
type GuildProposalStatus,
|
|
94
|
+
} from './guild'
|
|
81
95
|
export {
|
|
82
96
|
FAMILIES,
|
|
83
97
|
reduceListing,
|
package/src/kinds.ts
CHANGED
|
@@ -93,6 +93,21 @@ export const BUILT_IN_KINDS: KindDef[] = [
|
|
|
93
93
|
return i < 0 ? { id, params: {} } : { id: id.slice(i + 1), params: { owner: id.slice(0, i) } }
|
|
94
94
|
},
|
|
95
95
|
},
|
|
96
|
+
// A Guild proposal. Proposal ids count per Guild, so the cache id is `<guildAppId>/<proposalId>`.
|
|
97
|
+
{
|
|
98
|
+
kind: 'guild-proposal',
|
|
99
|
+
tags: ['GuildProposal'],
|
|
100
|
+
idProps: ['proposalId', 'id'],
|
|
101
|
+
params: ['appId'],
|
|
102
|
+
policy: 'live',
|
|
103
|
+
sources: ['algod'],
|
|
104
|
+
normalizeId: (id) => (/^(\d+\/)?\d+$/.test(id.trim()) ? id.trim() : undefined),
|
|
105
|
+
composeId: (id, params) => (params.appId && !id.includes('/') ? `${params.appId}/${id}` : id),
|
|
106
|
+
decomposeId: (id): { id: string; params: Record<string, string> } => {
|
|
107
|
+
const i = id.indexOf('/')
|
|
108
|
+
return i < 0 ? { id, params: {} } : { id: id.slice(i + 1), params: { appId: id.slice(0, i) } }
|
|
109
|
+
},
|
|
110
|
+
},
|
|
96
111
|
// Title is the author's own label in their post, not chain data.
|
|
97
112
|
hosted('private-content', 'PrivateContent', ['id'], 'slow', { params: ['title'] }),
|
|
98
113
|
// A page inside the same post. The host supplies its preview and navigation.
|
package/src/react/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { extractRefs } from '../grammar'
|
|
|
6
6
|
import type { IdentityData } from '../identity'
|
|
7
7
|
import type { AccountData, ApplicationData, AssetData, CollectionData } from '../sources/chain-data'
|
|
8
8
|
import type { NfdData } from '../sources/nfd'
|
|
9
|
+
import type { GuildProposalData } from '../guild'
|
|
9
10
|
import type { DualStakeData, PrivateContentData, RewardData, StakingPoolData, SubscriptionData, TradeData } from '../akita'
|
|
10
11
|
import type { AssetMediaData } from '../media'
|
|
11
12
|
import type { Entity, Ref } from '../types'
|
|
@@ -92,6 +93,8 @@ export const useSubscription = (id?: string, o?: UseEntityOptions) => useEntity<
|
|
|
92
93
|
export const useTrade = (offerId?: string, o?: UseEntityOptions) => useEntity<TradeData>('trade', offerId, o)
|
|
93
94
|
export const useDualStake = (appId?: string, o?: UseEntityOptions) => useEntity<DualStakeData>('dual-stake', appId, o)
|
|
94
95
|
export const usePrivateContent = (id?: string, o?: UseEntityOptions) => useEntity<PrivateContentData>('private-content', id, o)
|
|
96
|
+
/** A Guild proposal; `id` is `<guildAppId>/<proposalId>`. */
|
|
97
|
+
export const useGuildProposal = (id?: string, o?: UseEntityOptions) => useEntity<GuildProposalData>('guild-proposal', id, o)
|
|
95
98
|
export const usePrizeBox = (appId?: string, o?: UseEntityOptions) => useEntity<PrizeBoxData>('prize-box', appId, o)
|
|
96
99
|
|
|
97
100
|
/**
|
package/src/sources/algod.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { requestJson } from '../http'
|
|
|
5
5
|
import { refKey, type Endpoint, type Entity, type Ref } from '../types'
|
|
6
6
|
import { accountFromJson, assetFromJson, type ApplicationData } from './chain-data'
|
|
7
7
|
import { readReward, readStakingPool, readSubscription, readTrade, type SingletonApps } from '../akita'
|
|
8
|
+
import { readGuildProposal } from '../guild'
|
|
8
9
|
import type { Source, SourceContext } from './types'
|
|
9
10
|
|
|
10
11
|
interface AlgodApp {
|
|
@@ -12,7 +13,7 @@ interface AlgodApp {
|
|
|
12
13
|
params: { creator?: string; 'global-state'?: TealKeyValue[] }
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
const KINDS = new Set(['account', 'asset', 'application'])
|
|
16
|
+
const KINDS = new Set(['account', 'asset', 'application', 'guild-proposal'])
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Live state from an algod node: accounts, assets, apps, contract global state
|
|
@@ -41,6 +42,11 @@ export function createAlgodSource(endpoint: Endpoint, singletons: SingletonApps
|
|
|
41
42
|
if (a) out.set(refKey(ref), { ...base, data: accountFromJson(a), provenance: { ...provenance, round: Number(a.round ?? 0) || undefined } })
|
|
42
43
|
return out
|
|
43
44
|
}
|
|
45
|
+
if (ref.kind === 'guild-proposal') {
|
|
46
|
+
const data = await readGuildProposal(ctx.fetch, endpoint, ref.id)
|
|
47
|
+
if (data) out.set(refKey(ref), { ...base, data, provenance })
|
|
48
|
+
return out
|
|
49
|
+
}
|
|
44
50
|
const singleton = singletonKinds[ref.kind]
|
|
45
51
|
if (singleton) {
|
|
46
52
|
const data =
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/kinds.ts","../src/grammar.ts"],"sourcesContent":["import type { PolicyName } from './policy'\n\nexport interface KindDef {\n kind: string\n /** Tag names that store this kind in content. The first one is used when serializing. */\n tags: string[]\n /** Props that carry the id, in priority order. The first is used when serializing. */\n idProps: string[]\n /** Extra identifying props kept on the ref (never display data). */\n params?: string[]\n policy: PolicyName\n /** Source names to try, in order. Sources not configured on the client are skipped. */\n sources: string[]\n /** Contract family for chain sources (live state and archive reconstruction). */\n family?: string\n normalizeId?: (id: string) => string | undefined\n /** Builds the cache id from the tag id and params, for kinds keyed by more than one value. */\n composeId?: (id: string, params: Record<string, string>) => string\n /** Inverse of composeId, used when serializing a ref back to a tag. */\n decomposeId?: (id: string) => { id: string; params: Record<string, string> }\n}\n\nconst uint = (id: string) => (/^\\d+$/.test(id.trim()) ? String(BigInt(id.trim())) : undefined)\nconst address = (id: string) => (/^[A-Z2-7]{58}$/.test(id.trim()) ? id.trim() : undefined)\nconst nfdName = (id: string) => {\n const n = id.trim().toLowerCase()\n if (!n) return undefined\n return n.endsWith('.algo') ? n : `${n}.algo`\n}\nconst txid = (id: string) => (/^[A-Z2-7]{52}$/.test(id.trim()) ? id.trim() : undefined)\nconst any = (id: string) => (id.trim() ? id.trim() : undefined)\n\nconst contract = (kind: string, tag: string, family: string): KindDef => ({\n kind,\n tags: [tag],\n idProps: ['appId', 'id'],\n policy: 'live',\n sources: ['resolver', 'algod', 'indexer'],\n family,\n normalizeId: uint,\n})\n\nconst hosted = (kind: string, tag: string, idProps: string[], policy: PolicyName, extra: Partial<KindDef> = {}): KindDef => ({\n kind,\n tags: [tag],\n idProps,\n policy,\n sources: ['resolver'],\n normalizeId: any,\n ...extra,\n})\n\nexport const BUILT_IN_KINDS: KindDef[] = [\n { kind: 'account', tags: ['Account'], idProps: ['address', 'id'], policy: 'live', sources: ['algod', 'indexer'], normalizeId: address },\n { kind: 'identity', tags: [], idProps: ['address'], policy: 'slow', sources: ['identity'], normalizeId: address },\n { kind: 'asset', tags: ['ASA', 'Asset'], idProps: ['id', 'assetId'], policy: 'slow', sources: ['resolver', 'algod', 'indexer'], normalizeId: uint },\n { kind: 'application', tags: ['Application'], idProps: ['id', 'appId'], policy: 'slow', sources: ['resolver', 'indexer', 'algod'], normalizeId: uint },\n // A contract's ARC-56 interface: its `ui` actions and cards. Keyed by app id.\n { kind: 'interface', tags: [], idProps: ['appId', 'id'], policy: 'slow', sources: ['interface'], normalizeId: uint },\n { kind: 'asset-media', tags: [], idProps: ['id'], policy: 'slow', sources: ['media'], normalizeId: uint },\n { kind: 'transaction', tags: ['Transaction'], idProps: ['id', 'txId', 'txid'], policy: 'immutable', sources: ['indexer'], normalizeId: txid },\n { kind: 'nfd', tags: ['NFDProfile'], idProps: ['name', 'id'], policy: 'slow', sources: ['nfd'], normalizeId: nfdName },\n { kind: 'nfd-address', tags: [], idProps: ['address'], policy: 'slow', sources: ['nfd'], normalizeId: address },\n contract('listing', 'NFTListing', 'listing'),\n contract('auction', 'AuctionListing', 'auction'),\n contract('raffle', 'RaffleListing', 'raffle'),\n contract('poll', 'Poll', 'poll'),\n contract('prize-box', 'PrizeBox', 'prize-box'),\n hosted('collection', 'Collection', ['id', 'collectionId'], 'slow'),\n contract('staking-pool', 'StakingPool', 'staking-pool'),\n // HyperSwap is a singleton, so an offer id is enough. Older app tags also carry appId.\n // Chain first while the box exists; the resolver keeps closed offers and their participants.\n { kind: 'trade', tags: ['TradeOffer'], idProps: ['offerId', 'id'], params: ['appId'], policy: 'live', sources: ['algod', 'resolver'], normalizeId: uint },\n hosted('dual-stake', 'DualStakePool', ['appId', 'id'], 'live', { params: ['rewardAssetId'], normalizeId: uint }),\n // A disbursement id in the Rewards singleton.\n { kind: 'reward', tags: ['Reward'], idProps: ['id'], policy: 'live', sources: ['algod', 'resolver'], normalizeId: uint },\n // Services are numbered per owner, so the cache id is `<owner>/<serviceId>` when the owner is known.\n {\n kind: 'subscription',\n tags: ['Subscription'],\n idProps: ['id', 'serviceId'],\n params: ['owner', 'creator', 'address'],\n policy: 'slow',\n // Without an owner only the resolver can find the service.\n sources: ['algod', 'resolver'],\n normalizeId: (id) => (/^([A-Z2-7]{58}\\/)?\\d+$/.test(id.trim()) ? id.trim() : undefined),\n composeId: (id, params) => {\n const owner = params.owner ?? params.creator ?? params.address\n return owner && !id.includes('/') ? `${owner}/${id}` : id\n },\n decomposeId: (id): { id: string; params: Record<string, string> } => {\n const i = id.indexOf('/')\n return i < 0 ? { id, params: {} } : { id: id.slice(i + 1), params: { owner: id.slice(0, i) } }\n },\n },\n // Title is the author's own label in their post, not chain data.\n hosted('private-content', 'PrivateContent', ['id'], 'slow', { params: ['title'] }),\n // A page inside the same post. The host supplies its preview and navigation.\n { kind: 'page-card', tags: ['PageCard'], idProps: ['page', 'next', 'href', 'id'], params: ['title'], policy: 'immutable', sources: [], normalizeId: any },\n]\n\nexport class KindRegistry {\n private byKind = new Map<string, KindDef>()\n private byTag = new Map<string, KindDef>()\n\n constructor(defs: KindDef[] = BUILT_IN_KINDS) {\n defs.forEach((d) => this.register(d))\n }\n\n register(def: KindDef): void {\n this.byKind.set(def.kind, def)\n for (const tag of def.tags) this.byTag.set(tag, def)\n }\n\n get(kind: string): KindDef | undefined {\n return this.byKind.get(kind)\n }\n\n require(kind: string): KindDef {\n const def = this.byKind.get(kind)\n if (!def) throw new Error(`Unknown AlgoMD kind: ${kind}`)\n return def\n }\n\n forTag(tag: string): KindDef | undefined {\n return this.byTag.get(tag)\n }\n\n tags(): string[] {\n return [...this.byTag.keys()]\n }\n\n all(): KindDef[] {\n return [...this.byKind.values()]\n }\n}\n\nexport const defaultRegistry = new KindRegistry()\n","import { defaultRegistry, type KindRegistry } from './kinds'\nimport type { Ref, Size } from './types'\n\n/**\n * The AlgoMD content grammar. Stored content uses self-closing tags such as\n * `<ASA id=\"31566704\" />`. Existing posts are a compatibility boundary: tag\n * names, id props and legacy size props keep working.\n *\n * Only identifiers survive parsing. Any other prop is dropped, so content can\n * never supply the data that gets displayed.\n */\n\nexport interface RefSegment {\n type: 'ref'\n ref: Ref & { size?: Size }\n tag: string\n raw: string\n}\n\nexport interface TextSegment {\n type: 'text'\n content: string\n}\n\nexport type Segment = TextSegment | RefSegment\n\nconst PROPS = `((?:\"[^\"]*\"|'[^']*'|“[^”]*”|‘[^’]*’|\\\\{[^}]*\\\\}|[^'\"“”‘’>])*)`\nconst PROP_RE = /([A-Za-z_][\\w-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|\\{([^}]*)\\}|“([^”]*)”|‘([^’]*)’))?/g\nconst FENCE_RE = /```[\\s\\S]*?```/g\nconst ENTITIES: Record<string, string> = { amp: '&', quot: '\"', apos: \"'\", lt: '<', gt: '>' }\n\nconst tagRegexCache = new WeakMap<KindRegistry, { source: string; tags: string }>()\n\nfunction tagRegex(registry: KindRegistry): RegExp {\n const tags = registry.tags().sort((a, b) => b.length - a.length).join('|')\n let cached = tagRegexCache.get(registry)\n if (!cached || cached.tags !== tags) {\n cached = { tags, source: `<(${tags})\\\\b${PROPS}\\\\/>` }\n tagRegexCache.set(registry, cached)\n }\n return new RegExp(cached.source, 'g')\n}\n\nfunction decodeEntities(value: string): string {\n return value.replace(/&(amp|quot|apos|lt|gt);/g, (_, e: string) => ENTITIES[e] ?? _)\n}\n\nexport function parseProps(raw: string): Record<string, string> {\n const props: Record<string, string> = {}\n for (const m of raw.matchAll(PROP_RE)) {\n const name = m[1]!\n props[name] = decodeEntities(m[2] ?? m[3] ?? m[4] ?? m[5] ?? m[6] ?? 'true').trim()\n }\n return props\n}\n\n/** Maps current and legacy size props to a size. */\nexport function sizeFromProps(props: Record<string, string>): Size | undefined {\n const size = props.size?.toLowerCase()\n switch (size) {\n case 'inline':\n case 'small':\n case 'large':\n return size\n case 'xs':\n case 'sm':\n return 'inline'\n // Legacy `full` meant a full-width card. The full-screen size is stored as `fullscreen`.\n case 'md':\n case 'lg':\n case 'xl':\n case 'full':\n return 'large'\n case 'fullscreen':\n return 'full'\n }\n if (props.inline === 'true' || props.compact === 'true') return 'inline'\n return undefined\n}\n\n/** Builds a ref from a tag name and its props, or undefined if the id is missing or malformed. */\nexport function refFromTag(tag: string, props: Record<string, string>, registry: KindRegistry = defaultRegistry): (Ref & { size?: Size }) | undefined {\n const def = registry.forTag(tag)\n if (!def) return undefined\n const rawId = def.idProps.map((p) => props[p]).find((v) => v && v !== 'true')\n if (!rawId) return undefined\n const params = Object.fromEntries((def.params ?? []).filter((p) => props[p]).map((p) => [p, props[p]!]))\n const composed = def.composeId ? def.composeId(rawId, params) : rawId\n const id = def.normalizeId ? def.normalizeId(composed) : composed\n if (!id) return undefined\n const ref: Ref & { size?: Size } = { kind: def.kind, id }\n if (Object.keys(params).length) ref.params = params\n const size = sizeFromProps(props)\n if (size) ref.size = size\n return ref\n}\n\n/** Splits content into text and reference segments. Tags inside fenced code stay text. */\nexport function parseContent(content: string, registry: KindRegistry = defaultRegistry): Segment[] {\n if (!content) return []\n const fences = [...content.matchAll(FENCE_RE)].map((m) => [m.index!, m.index! + m[0].length] as const)\n const inFence = (i: number) => fences.some(([s, e]) => i >= s && i < e)\n\n const out: Segment[] = []\n let last = 0\n for (const m of content.matchAll(tagRegex(registry))) {\n const at = m.index!\n if (inFence(at)) continue\n const ref = refFromTag(m[1]!, parseProps(m[2] ?? ''), registry)\n if (!ref) continue\n if (at > last) out.push({ type: 'text', content: content.slice(last, at) })\n out.push({ type: 'ref', ref, tag: m[1]!, raw: m[0] })\n last = at + m[0].length\n }\n if (last < content.length) out.push({ type: 'text', content: content.slice(last) })\n return out\n}\n\n/** Every distinct reference in the content, in order of first appearance. */\nexport function extractRefs(content: string, registry: KindRegistry = defaultRegistry): Ref[] {\n const seen = new Map<string, Ref>()\n for (const s of parseContent(content, registry)) {\n if (s.type !== 'ref') continue\n const key = `${s.ref.kind}:${s.ref.id}`\n if (!seen.has(key)) seen.set(key, { kind: s.ref.kind, id: s.ref.id, ...(s.ref.params ? { params: s.ref.params } : {}) })\n }\n return [...seen.values()]\n}\n\nexport function hasRefs(content: string, registry: KindRegistry = defaultRegistry): boolean {\n return parseContent(content, registry).some((s) => s.type === 'ref')\n}\n\nconst attr = (v: string) => v.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>')\n\n/**\n * Serializes a reference as a tag. Sizes are written in a form that older\n * clients also understand: inline as `compact`, full as `size=\"fullscreen\"`.\n */\nexport function serializeRef(ref: Ref & { size?: Size }, registry: KindRegistry = defaultRegistry): string {\n const def = registry.require(ref.kind)\n const tag = def.tags[0]\n if (!tag) throw new Error(`Kind ${ref.kind} has no tag and cannot be stored in content`)\n const split = def.decomposeId ? def.decomposeId(ref.id) : { id: ref.id, params: {} as Record<string, string> }\n const params = { ...split.params, ...ref.params }\n const parts = [`${def.idProps[0]}=\"${attr(split.id)}\"`]\n for (const p of def.params ?? []) if (params[p]) parts.push(`${p}=\"${attr(params[p])}\"`)\n if (ref.size === 'inline') parts.push('compact')\n else if (ref.size === 'small') parts.push('size=\"small\"')\n else if (ref.size === 'full') parts.push('size=\"fullscreen\"')\n return `<${tag} ${parts.join(' ')} />`\n}\n"],"mappings":"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","names":[]}
|