From ee69c8e91dc2c9d36a6a4e4743dd858ab9908d77 Mon Sep 17 00:00:00 2001
From: Jack Frain
Date: Wed, 6 Nov 2024 16:16:04 -0500
Subject: [PATCH 1/3] feat(specs): if not vouched, vouch before stamping
---
src/dal/index.ts | 2 +-
src/lib/index.ts | 23 +++++++++++----
src/pages/home/index.tsx | 38 ++++++++++++++++++++++--
src/pages/show/index.tsx | 36 +++++++++++++++++++++-
src/services/ao.ts | 64 ++++++++++++++++++++++++++++++++++++++--
src/services/index.ts | 3 +-
src/services/vouched.ts | 29 ------------------
src/services/warp.ts | 0
8 files changed, 152 insertions(+), 43 deletions(-)
delete mode 100644 src/services/vouched.ts
delete mode 100644 src/services/warp.ts
diff --git a/src/dal/index.ts b/src/dal/index.ts
index 06ab869..b439f0f 100644
--- a/src/dal/index.ts
+++ b/src/dal/index.ts
@@ -52,7 +52,7 @@ const stampCountSchema = z.function().args(z.string())
const isVouchedSchema = z
.function()
.args(z.string())
- .returns(z.promise(z.boolean()))
+ .returns(z.promise(z.object({ addr: z.string(), vouched: z.boolean() })))
const querySchema = z.function().args(z.string()).returns(z.promise(z.array(AoSpecSchema.optional())))
diff --git a/src/lib/index.ts b/src/lib/index.ts
index 2f9dafd..67bb76b 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -43,6 +43,7 @@ export default {
const query = fromPromise(querySchema.implement(services.query))
const queryRelated = fromPromise(queryRelatedSchema.implement(services.queryRelated))
const stamp = fromPromise(stampSchema.implement(services.stamp))
+ const isVouched = fromPromise(isVouchedSchema.implement(services.isVouched))
const stampCounts = fromPromise(stampCountsSchema.implement(services.stampCounts))
return {
@@ -161,12 +162,22 @@ export default {
)
.map(uniqBy(prop("id")))
},
- stamp: (tx: string) =>
- connect()
- //.chain(isVouched) // isVouched
- .chain(
- (addr: string) => stamp(tx, addr)
- ),
+ stamp: (tx: string) => {
+ return connect()
+ .chain(isVouched)
+ .chain(({ addr, vouched }) => {
+ if (vouched) {
+ return Resolved({ addr })
+ }
+ return Rejected('Not Vouched')
+ })
+ .bichain(
+ Rejected,
+ ({ addr }) => {
+ return stamp(tx, addr)
+ },
+ )
+ }
}
},
}
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx
index bc2b81f..945b6b1 100644
--- a/src/pages/home/index.tsx
+++ b/src/pages/home/index.tsx
@@ -9,6 +9,7 @@ import Loading from '../../components/loading'
const HomePage = () => {
const [showError, setShowError] = useState(false)
+ const [showVouchModal, setShowVouchModal] = useState(false)
const [copying, setCopying] = useState(false)
const s = useHomeService()
@@ -16,7 +17,11 @@ const HomePage = () => {
const send = s[1]
const context = useMemo(() => {
if (s[0].context?.error) {
- setShowError(true)
+ if (s[0].context.error.message === 'Not Vouched') {
+ setShowVouchModal(true)
+ } else {
+ setShowError(true)
+ }
}
return s[0].context
}, [s])
@@ -121,7 +126,7 @@ const HomePage = () => {
tx={context?.selected?.id}
/>
-
+ setShowError(!showError)} />
{showError && (
@@ -140,6 +145,35 @@ const HomePage = () => {
)}
+ setShowVouchModal(!showVouchModal)} />
+ {showVouchModal && (
+
+
+
You are not vouched!
+
You must be vouched to stamp Specs.
+
+
+
+
+ )}
)
}
diff --git a/src/pages/show/index.tsx b/src/pages/show/index.tsx
index c4d22de..4e755f7 100644
--- a/src/pages/show/index.tsx
+++ b/src/pages/show/index.tsx
@@ -8,11 +8,16 @@ const shortHash = (h: string) => `${take(5, h)}...${takeLast(5, h)}`
const ShowPage = ({ tx, parent = false }: { tx: string, parent?: boolean }) => {
const [showError, setShowError] = useState(false)
+ const [showVouchModal, setShowVouchModal] = useState(false)
const s = useShowService()
const send = s[1]
const context = useMemo(() => {
if (s[0].context?.error) {
- setShowError(true)
+ if (s[0].context.error.message === 'Not Vouched') {
+ setShowVouchModal(true)
+ } else {
+ setShowError(true)
+ }
}
return s[0].context
}, [s])
@@ -158,6 +163,35 @@ const ShowPage = ({ tx, parent = false }: { tx: string, parent?: boolean }) => {
)}
+ setShowVouchModal(!showVouchModal)} />
+ {showVouchModal && (
+
+
+
You are not vouched!
+
You must be vouched to stamp Specs.
+
+
+
+
+ )}
>
)
}
diff --git a/src/services/ao.ts b/src/services/ao.ts
index 2c00f77..a9855cb 100644
--- a/src/services/ao.ts
+++ b/src/services/ao.ts
@@ -1,7 +1,15 @@
-import { createDataItemSigner, dryrun, message } from "@permaweb/aoconnect"
+import { createDataItemSigner, dryrun, message, result } from "@permaweb/aoconnect"
+import { compose, head, propOr } from "ramda"
import { Spec } from "src/types/Spec"
const SPEC_PID = "6x68KURcD4ySOslFCxiIorjsbpzNy6WD4joH6C8VHgg"
-
+const VOUCH_PID = "ZTTO02BL2P-lseTLUgiIPD9d0CF1sc4LbMA2AQ7e9jo"
+const VOUCHER_WHITELIST = [
+ "Ax_uXyLQBPZSQ15movzv9-O1mDo30khslqN64qD27Z8", // Vouch-X
+ "k6p1MtqYhQQOuTSfN8gH7sQ78zlHavt8dCDL88btn9s", // Vouch-Gitcoin-Passport
+ "QeXDjjxcui7W2xU08zOlnFwBlbiID4sACpi0tSS3VgY", // Vouch-AO-Balance
+ "3y0YE11i21hpP8UY0Z1AVhtPoJD4V_AbEBx-g0j9wRc", // Vouch-wAR-Stake
+]
+const MIN_VOUCH_SCORE = 2
export const upload = async (md: {
data: string
tags: {
@@ -72,6 +80,7 @@ export const upload = async (md: {
return result
}
+
export const query = async (tx: string) => {
const args = {
process: SPEC_PID,
@@ -126,4 +135,55 @@ export const queryRelated = async (tx: string) => {
const data: Spec[] = JSON.parse(result.Output.data)
return data
+}
+
+export const isVouched = async (tx: string) => {
+ const args = {
+ process: VOUCH_PID,
+ tags: [
+ {
+ name: "Action",
+ value: "Get-Vouches"
+ },
+ {
+ name: "ID",
+ value: tx
+ }
+ ],
+ signer: createDataItemSigner(window.arweaveWallet)
+ }
+
+ const messageId = await message(args)
+
+ const resultArgs = {
+ process: VOUCH_PID,
+ message: messageId,
+ }
+ const messageResult = await result(resultArgs)
+
+ const vouchers = compose(
+ propOr({}, 'Vouchers'),
+ JSON.parse,
+ propOr('{}', 'Data'),
+ head,
+ propOr([], 'Messages')
+ )(messageResult)
+
+ let vouchScore = 0
+ for (const voucher of Object.keys(vouchers)) {
+ if (VOUCHER_WHITELIST.includes(voucher)) {
+ const vouch = vouchers[voucher]
+ const vouchFor = vouch['Vouch-For']
+ if (vouchFor != tx) {
+ throw new Error('Vouch has Vouch-For mismatch')
+ }
+ const valueStr = vouch['Value'].match(/^(\d+\.\d+)|(\d+)/g)?.[0] ?? "0"
+ const value = parseFloat(valueStr)
+ if (valueStr == null || value == null) {
+ throw new Error('Vouch has invalid Value')
+ }
+ vouchScore += value
+ }
+ }
+ return { addr: tx, vouched: Boolean(vouchScore > MIN_VOUCH_SCORE) }
}
\ No newline at end of file
diff --git a/src/services/index.ts b/src/services/index.ts
index 19c8f15..8d54ab9 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -1,8 +1,7 @@
import { getActiveAddress } from "./wallet";
import { post, gql, get } from "./arweave";
import { stampCounts, stamp, stampCount } from "./stamps";
-import { isVouched } from "./vouched";
-import { query, queryAll, queryRelated, upload } from "./ao"
+import { isVouched, query, queryAll, queryRelated, upload } from "./ao"
import { Services } from "../dal"
diff --git a/src/services/vouched.ts b/src/services/vouched.ts
deleted file mode 100644
index 243caf3..0000000
--- a/src/services/vouched.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { getHost } from "./get-host";
-import { path } from "ramda";
-
-export const isVouched = (addr) =>
- fetch(`https://${getHost()}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- query: `
-query ($addresses: [String!]!) {
- transactions(tags:[{name: "Vouch-For", values: $addresses}]) {
- edges {
- node {
- id
- }
- }
- }
-}
- `,
- variables: {
- addresses: [addr],
- },
- }),
- })
- .then((res) => res.json())
- .then(path(["data", "transactions", "edges"]))
- .then((edges: { length: number }) => edges.length > 0);
\ No newline at end of file
diff --git a/src/services/warp.ts b/src/services/warp.ts
deleted file mode 100644
index e69de29..0000000
From bcc739e9b5291e08a8914a02aa383eeb588f00eb Mon Sep 17 00:00:00 2001
From: Jack Frain
Date: Wed, 6 Nov 2024 16:25:47 -0500
Subject: [PATCH 2/3] show default spec on create
---
src/pages/form/index.tsx | 7 ++++---
src/pages/form/service.ts | 2 +-
src/pages/home/index.tsx | 1 -
src/pages/show/index.tsx | 1 -
4 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/pages/form/index.tsx b/src/pages/form/index.tsx
index 0f245b9..5e779aa 100644
--- a/src/pages/form/index.tsx
+++ b/src/pages/form/index.tsx
@@ -122,10 +122,11 @@ const EditorComponent: preact.FunctionComponent = ({ tx }) => {
};
useEffect(() => {
- if (current === "ready" && !loaded && context.spec && context.spec[0]) {
+ console.log(2, { current, loaded, context })
+ if (current === "ready" && !loaded && context.spec) {
setLoaded(true);
- const specData = context.spec[0];
+ const specData = context.spec;
setSpecMeta({
Title: specData.Title,
@@ -138,7 +139,7 @@ const EditorComponent: preact.FunctionComponent = ({ tx }) => {
});
if (editor && editor.value() === "") {
- editor.value(specData.html)
+ editor.value(specData.body)
}
}
diff --git a/src/pages/form/service.ts b/src/pages/form/service.ts
index 79150e8..a3e4207 100644
--- a/src/pages/form/service.ts
+++ b/src/pages/form/service.ts
@@ -33,7 +33,7 @@ const machine = createMachine({
"done",
"ready",
reduce((ctx: FormMachineContext, ev: FormMachineEvent) => {
- return { ...ctx, spec: ev.data }
+ return { ...ctx, spec: ev.data[0] ? { ...ev.data[0], body: ev.data[0].html } : ev.data }
}),
),
),
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx
index 945b6b1..a56e5f7 100644
--- a/src/pages/home/index.tsx
+++ b/src/pages/home/index.tsx
@@ -154,7 +154,6 @@ const HomePage = () => {
{
- route("/", true)
+ route(`/view/${context.txId}`)
}}>
ok
diff --git a/src/pages/form/service.ts b/src/pages/form/service.ts
index a3e4207..2f46a00 100644
--- a/src/pages/form/service.ts
+++ b/src/pages/form/service.ts
@@ -10,6 +10,7 @@ import yaml from 'js-yaml'
import services from "../../services"
import Api from "../../lib"
import { FormMachineContext, FormMachineCurrent, FormMachineEvent, FormMachineSend } from "./types"
+import { ZodError } from "zod"
const api = Api.init(services)
@@ -60,17 +61,22 @@ const machine = createMachine({
${ctx.md}`,
)
- // add saved doc to local cache -- hold for now...
- //.map(({ id }) => (cache.update(assoc(id, ctx.md)), { id }))
- .map(({ id }) => ({ ...ctx, id }))
.toPromise()
},
- transition("done", "confirm"),
+ transition(
+ "done",
+ "confirm",
+ reduce((ctx: FormMachineContext, ev: FormMachineEvent) => {
+ const { txId } = ev.data as { txId?: string }
+ return { ...ctx, txId }
+ }),
+ ),
transition(
"error",
"ready",
reduce((ctx: FormMachineContext, ev: FormMachineEvent) => {
- return { ...ctx, error: ev.error }
+ const { error } = ev.data as { error?: string }
+ return { ...ctx, saveError: error }
}),
),
),
diff --git a/src/pages/form/types.ts b/src/pages/form/types.ts
index 39aca7c..047b4e7 100644
--- a/src/pages/form/types.ts
+++ b/src/pages/form/types.ts
@@ -4,19 +4,22 @@ import { ZodError } from "zod"
export interface FormMachineContext {
type?: string
tx?: string | null
+ txId?: string | null
spec?: FormSpec
md?: string
metadata?: Metadata
error?: ZodError
+ saveError?: string
id?: string
}
export interface FormMachineEvent {
type: string
md?: string
- data?: FormSpec
+ data?: FormSpec | { txId?: string, error?: string }
metadata?: Metadata
error?: ZodError
+ saveError?: string
[key: string]: unknown
}
diff --git a/src/pages/home/service.ts b/src/pages/home/service.ts
index 70ebb0f..1d79bb3 100644
--- a/src/pages/home/service.ts
+++ b/src/pages/home/service.ts
@@ -50,7 +50,6 @@ const machine = createMachine({
transition(
"done",
"view",
- // TODO: fix this type when stamping is working
reduce((ctx: HomeMachineContext, ev: { data: number }) => {
const specs = ctx.specs.map((s) =>
s.id === ctx.selected.id ? assoc("stamps", ev.data, s) : s,
diff --git a/src/services/ao.ts b/src/services/ao.ts
index a9855cb..dadd3e9 100644
--- a/src/services/ao.ts
+++ b/src/services/ao.ts
@@ -10,6 +10,7 @@ const VOUCHER_WHITELIST = [
"3y0YE11i21hpP8UY0Z1AVhtPoJD4V_AbEBx-g0j9wRc", // Vouch-wAR-Stake
]
const MIN_VOUCH_SCORE = 2
+const getFirstMessage = compose(head, propOr([], 'Messages'))
export const upload = async (md: {
data: string
tags: {
@@ -17,9 +18,10 @@ export const upload = async (md: {
value: string
}[]
}) => {
- const getTag = (n: string): string | undefined =>
- md.tags.find(tag => tag.name === n)?.value
+ const getTag = (tags: Array<{ name: string, value: string }>) => (n: string): string | undefined =>
+ tags.find(tag => tag.name === n)?.value
+ const getMetadataTag = getTag(md.tags)
const args = {
process: SPEC_PID,
tags: [
@@ -29,56 +31,62 @@ export const upload = async (md: {
},
{
name: "Spec-DataProtocol",
- value: getTag('Data-Protocol')
+ value: getMetadataTag('Data-Protocol')
},
{
name: "Spec-GroupId",
- value: getTag('GroupId')
+ value: getMetadataTag('GroupId')
},
{
name: "Spec-Variant",
- value: getTag('Variant')
+ value: getMetadataTag('Variant')
},
{
name: "Spec-Title",
- value: getTag('Title')
+ value: getMetadataTag('Title')
},
{
name: "Spec-Description",
- value: getTag('Description')
+ value: getMetadataTag('Description')
},
{
name: "Spec-Topics",
- value: getTag('Topics').toString()
+ value: getMetadataTag('Topics').toString()
},
{
name: "Spec-Authors",
- value: getTag('Authors').toString()
+ value: getMetadataTag('Authors').toString()
},
{
name: "Spec-Type",
- value: getTag('Type')
+ value: getMetadataTag('Type')
},
{
name: "Spec-Forks",
- value: getTag('Forks')
+ value: getMetadataTag('Forks')
},
{
name: "Spec-Content-Type",
- value: getTag('Content-Type')
+ value: getMetadataTag('Content-Type')
},
{
name: "Spec-Render-With",
- value: getTag('Render-With')
+ value: getMetadataTag('Render-With')
},
],
data: md.data,
signer: createDataItemSigner(window.arweaveWallet)
}
- const result = await message(args)
+ const messageId = await message(args)
+ const messageResult = await result({ process: SPEC_PID, message: messageId })
+ const tags = compose(
+ propOr([], 'Tags'),
+ getFirstMessage
+ )(messageResult) as Array<{ name: string, value: string }>
- return result
+ const getResultTag = getTag(tags)
+ return { status: getResultTag('Result'), error: getResultTag('Error'), txId: getResultTag('ID') }
}
export const query = async (tx: string) => {
@@ -165,8 +173,7 @@ export const isVouched = async (tx: string) => {
propOr({}, 'Vouchers'),
JSON.parse,
propOr('{}', 'Data'),
- head,
- propOr([], 'Messages')
+ getFirstMessage
)(messageResult)
let vouchScore = 0
@@ -177,6 +184,7 @@ export const isVouched = async (tx: string) => {
if (vouchFor != tx) {
throw new Error('Vouch has Vouch-For mismatch')
}
+ // The value is a string like "4.5-USD" or "5-USD". We want to match the number.
const valueStr = vouch['Value'].match(/^(\d+\.\d+)|(\d+)/g)?.[0] ?? "0"
const value = parseFloat(valueStr)
if (valueStr == null || value == null) {
@@ -185,5 +193,5 @@ export const isVouched = async (tx: string) => {
vouchScore += value
}
}
- return { addr: tx, vouched: Boolean(vouchScore > MIN_VOUCH_SCORE) }
+ return { addr: tx, vouched: vouchScore > MIN_VOUCH_SCORE }
}
\ No newline at end of file
diff --git a/src/services/stamps.ts b/src/services/stamps.ts
index 6a7d073..95cc6a9 100644
--- a/src/services/stamps.ts
+++ b/src/services/stamps.ts
@@ -9,16 +9,17 @@ export const stampCounts = (txs: string[]) => {
export const stamp = (tx: string) => {
return stamps.hasStamped(tx).then((s) => {
- return !s
- ? stamps
- .stamp(tx)
- .then(() => new Promise((r) => setTimeout(r, 500)))
- .then(() => stamps.count(tx).then(prop("vouched")))
- : Promise.reject("Already Stamped!")
+ if (s) {
+ return Promise.reject('Already Stamped!')
}
- )
+ const addOne = (n: number) => n + 1
+ const count = stampCount(tx).then(addOne)
+ return stamps
+ .stamp(tx)
+ .then(() => count)
+ })
}
-export const stampCount = (tx) => {
+export const stampCount = (tx: string) => {
return stamps.count(tx).then(prop("vouched"));
}