Skip to content

Commit

Permalink
Merge pull request #103 from art-by-city/100-following
Browse files Browse the repository at this point in the history
Adds Following module
  • Loading branch information
jim-toth authored Nov 17, 2023
2 parents 61ac193 + 9dd9821 commit 9805b52
Show file tree
Hide file tree
Showing 13 changed files with 365 additions and 10 deletions.
8 changes: 8 additions & 0 deletions src/client/authenticated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import { AuthenticatedArtByCityCurations } from '../curations'
import { AuthenticatedArFSClient } from '../arfs'
import { AuthenticatedArtByCityPublications } from '../publications'
import BaseArtByCityClient from './base'
import { AuthenticatedArtByCityFollowing } from '../following'

export default class AuthenticatedArtByCityClient extends BaseArtByCityClient {
declare public readonly curations: AuthenticatedArtByCityCurations
declare public readonly arfs: AuthenticatedArFSClient
declare public readonly publications: AuthenticatedArtByCityPublications
declare public readonly following: AuthenticatedArtByCityFollowing
declare public readonly signer: ArweaveSigner | InjectedArweaveSigner

constructor(
Expand Down Expand Up @@ -56,5 +58,11 @@ export default class AuthenticatedArtByCityClient extends BaseArtByCityClient {
this.config,
this.signer
)
this.following = new AuthenticatedArtByCityFollowing(
this.arweave,
this.warp,
this.config,
this.signer
)
}
}
10 changes: 9 additions & 1 deletion src/client/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ArtByCityUsernames } from '../usernames'
import { ArFSClient } from '../arfs'
import TransactionsModule from '../common/transactions'
import { ArtByCityPublications } from '../publications'
import { ArtByCityFollowing } from '../following'

export default class ArtByCity {
public readonly arweave!: Arweave
Expand All @@ -24,6 +25,7 @@ export default class ArtByCity {
public readonly arfs!: ArFSClient
public readonly transactions!: TransactionsModule
public readonly publications!: ArtByCityPublications
public readonly following!: ArtByCityFollowing

constructor(arweave?: Arweave, config?: Partial<ArtByCityConfig>) {
const environment = config?.environment || 'production'
Expand All @@ -39,7 +41,8 @@ export default class ArtByCity {
this.arweave = arweave || Arweave.init({})
this.warp = environment !== 'development'
? WarpFactory.forMainnet({ inMemory: true, dbLocation: '.art-by-city' })
: WarpFactory.forLocal()
/* @ts-expect-error warp type spaghetti */
: WarpFactory.forLocal(1984, this.arweave)
this.warp = this.warp.use(new DeployPlugin())
this.legacy = new ArtByCityLegacy(this.arweave, this.config)
this.curations = new ArtByCityCurations(
Expand All @@ -58,5 +61,10 @@ export default class ArtByCity {
this.arfs,
this.config
)
this.following = new ArtByCityFollowing(
this.arweave,
this.warp,
this.config
)
}
}
9 changes: 6 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export type ArtByCityConfig = {
whitelist: string
collaborative: string
collaborativeWhitelist: string
}
},
following: string
}
cache: {
type: CacheStrategy
Expand All @@ -34,7 +35,8 @@ export const DEFAULT_ARTBYCITY_CONFIG: ArtByCityConfig = {
whitelist: 'wQyCsSM-gROX2iVUaezrfNJg-z9MJdy_FCJB5oEx8cY',
collaborative: 'O_SmOtUfUNYf8Z8BqFao1MzBM_9Ydh8djkcDAV2u7eM',
collaborativeWhitelist: '3M_dT7kclnFtiITTMDRKvrbfAZEWWZb05KWH6OojEWs'
}
},
following: 'uPPmKBhY4L4MKAaGi2pCDU30nnEo9VtMb9Sw-zSApFY'
},
cache: {
type: 'memcache'
Expand All @@ -51,7 +53,8 @@ export const ARTBYCITY_STAGING_CONFIG: ArtByCityConfig = {
whitelist: 'wQyCsSM-gROX2iVUaezrfNJg-z9MJdy_FCJB5oEx8cY',
collaborative: 'O_SmOtUfUNYf8Z8BqFao1MzBM_9Ydh8djkcDAV2u7eM',
collaborativeWhitelist: '3M_dT7kclnFtiITTMDRKvrbfAZEWWZb05KWH6OojEWs'
}
},
following: 'uPPmKBhY4L4MKAaGi2pCDU30nnEo9VtMb9Sw-zSApFY'
},
cache: {
type: 'memcache'
Expand Down
2 changes: 1 addition & 1 deletion src/curations/authenticated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default class AuthenticatedArtByCityCurations
{
constructor(
arweave: Arweave,
protected warp: Warp,
protected readonly warp: Warp,
config: ArtByCityConfig,
private readonly signer: ArweaveSigner | InjectedArweaveSigner
) {
Expand Down
117 changes: 117 additions & 0 deletions src/following/authenticated-following.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import Arweave from 'arweave'
import { Tag, Warp } from 'warp-contracts'
import { ArweaveSigner } from 'warp-arbundles'
import { InjectedArweaveSigner, isSigner } from 'warp-contracts-plugin-deploy'

import { ArtByCityConfig } from '../config'
import { getAddressFromSigner } from '../util/crypto'
import {
ArtByCityFollowing,
FollowInteraction,
FollowingCreationOptions,
UnfollowInteraction
} from './'

export default class AuthenticatedArtByCityFollowing
extends ArtByCityFollowing
{
constructor(
protected readonly arweave: Arweave,
protected readonly warp: Warp,
protected readonly config: ArtByCityConfig,
private readonly signer: ArweaveSigner | InjectedArweaveSigner
) {
super(arweave, warp, config)
}

async getOrCreate() {
const owner = await getAddressFromSigner(this.signer)
const contract = await this.getContract(owner)

if (contract) {
return contract.txId()
}

return await this.create()
}

async create(opts?: FollowingCreationOptions) {
if (!opts) {
opts = {}
}

if (!opts.owner) {
opts.owner = await getAddressFromSigner(this.signer)
}

const initialState = {
owner: opts.owner,
following: opts.following || []
}

const tags = (opts.tags || []).map<Tag>(tag => new Tag(tag.name, tag.value))
tags.push(
new Tag('Protocol', 'ArtByCity'),
new Tag('Contract-Name', 'Following'),
new Tag('Contract-Version', '0.0.1'),
new Tag('Entity-Type', 'following')
)

const { contractTxId } = await this.warp.deployFromSourceTx({
/* @ts-expect-error warp types are spaghetti */
wallet: this.signer,
srcTxId: this.config.contracts.following,
initState: JSON.stringify(initialState),
tags
/* @ts-expect-error warp types are spaghetti */
}, !isSigner(this.signer))

return contractTxId
}

async follow(address: string) {
const owner = await getAddressFromSigner(this.signer)
const contract = await this.getContract(owner)

if (contract) {
const res = await contract
/* @ts-expect-error warp spaghetti */
.connect(this.signer)
.writeInteraction<FollowInteraction>({
function: 'follow',
address
})

if (!res) {
throw new Error(`Error interacting with Following contract`)
}

return res
}

throw new Error(`No Following contract found for ${owner}`)
}

async unfollow(address: string) {
const owner = await getAddressFromSigner(this.signer)
const contract = await this.getContract(owner)

if (contract) {
const res = await contract
/* @ts-expect-error warp spaghetti */
.connect(this.signer)
.writeInteraction<UnfollowInteraction>({
function: 'unfollow',
address
})

if (!res) {
throw new Error(`Error interacting with Following contract`)
}

return res
}

throw new Error(`No Following contract found for ${owner}`)
}
}
52 changes: 52 additions & 0 deletions src/following/following.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import ArDB from 'ardb'
import ArdbTransaction from 'ardb/lib/models/transaction'
import Arweave from 'arweave'
import { Warp } from 'warp-contracts'

import TransactionsModule from '../common/transactions'
import { ArtByCityConfig } from '../config'
import { FollowingContractState } from './'

export default class ArtByCityFollowing {
protected readonly ardb!: ArDB
protected readonly transactions!: TransactionsModule

constructor(
protected readonly arweave: Arweave,
protected readonly warp: Warp,
protected readonly config: ArtByCityConfig
) {
this.ardb = new ArDB(this.arweave)
this.transactions = new TransactionsModule(arweave)
}

async getContract(owner: string) {
const txs = await this.ardb
.search('transactions')
.from(owner)
.tag('Contract-Name', 'Following')
.sort('HEIGHT_DESC')
.limit(1)
.find() as ArdbTransaction[]

const tx = txs.at(0)

if (tx) {
return this.warp.contract<FollowingContractState>(tx.id)
}

return null
}

async following(owner: string) {
const contract = await this.getContract(owner)

if (contract) {
const { cachedValue: { state } } = await contract.readState()

return state.following
}

return []
}
}
26 changes: 26 additions & 0 deletions src/following/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export { default as ArtByCityFollowing } from './following'
export {
default as AuthenticatedArtByCityFollowing
} from './authenticated-following'

export interface FollowingCreationOptions {
owner?: string
following?: string[]
tags?: { name: string, value: string }[]
slug?: boolean | string
}

export interface FollowingContractState {
owner: string
following: string[]
}

export interface FollowInteraction {
function: 'follow'
address: string
}

export interface UnfollowInteraction {
function: 'unfollow'
address: string
}
Loading

0 comments on commit 9805b52

Please sign in to comment.