Skip to content

Commit 67df06a

Browse files
committed
core: arweave state reader
1 parent 3411232 commit 67df06a

4 files changed

Lines changed: 521 additions & 3 deletions

File tree

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
export interface Options {
2+
readonly namespace?: string
3+
readonly owners?: string[]
4+
readonly arweaveUrl?: string
5+
readonly graphqlUrl?: string
6+
readonly rateLimitRetryDelayMs?: number
7+
}
8+
9+
export const defaults = {
10+
namespace: 'Sequence-Sessions',
11+
owners: ['AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM'],
12+
arweaveUrl: 'https://arweave.net',
13+
graphqlUrl: 'https://arweave.net/graphql',
14+
rateLimitRetryDelayMs: 5 * 60 * 1000,
15+
}
16+
17+
export async function findItems(
18+
filter: { [name: string]: undefined | string | string[] },
19+
options?: Options & { pageSize?: number; maxResults?: number },
20+
): Promise<{ [id: string]: { [tag: string]: string } }> {
21+
const namespace = options?.namespace ?? defaults.namespace
22+
const owners = options?.owners
23+
const graphqlUrl = options?.graphqlUrl ?? defaults.graphqlUrl
24+
const rateLimitRetryDelayMs = options?.rateLimitRetryDelayMs ?? defaults.rateLimitRetryDelayMs
25+
const pageSize = options?.pageSize ?? 100
26+
const maxResults = options?.maxResults
27+
28+
const tags = Object.entries(filter).flatMap(([name, values]) =>
29+
values === undefined
30+
? []
31+
: [
32+
`{ name: "${namespace ? `${namespace}-${name}` : name}", values: [${typeof values === 'string' ? `"${values}"` : values.map((value) => `"${value}"`).join(', ')}] }`,
33+
],
34+
)
35+
36+
const edges: Array<{ cursor: string; node: { id: string; tags: Array<{ name: string; value: string }> } }> = []
37+
38+
for (let hasNextPage = true; hasNextPage && (maxResults === undefined || edges.length < maxResults); ) {
39+
const query = `
40+
query {
41+
transactions(sort: HEIGHT_DESC, ${edges.length ? `first: ${pageSize}, after: "${edges[edges.length - 1]!.cursor}"` : `first: ${pageSize}`}, tags: [${tags.join(', ')}]${owners === undefined ? '' : `, owners: [${owners.map((owner) => `"${owner}"`).join(', ')}]`}) {
42+
pageInfo {
43+
hasNextPage
44+
}
45+
edges {
46+
cursor
47+
node {
48+
id
49+
tags {
50+
name
51+
value
52+
}
53+
}
54+
}
55+
}
56+
}
57+
`
58+
59+
let response: Response
60+
while (true) {
61+
response = await fetch(graphqlUrl, {
62+
method: 'POST',
63+
headers: { 'Content-Type': 'application/json' },
64+
body: JSON.stringify({ query }),
65+
redirect: 'follow',
66+
})
67+
if (response.status !== 429) {
68+
break
69+
}
70+
console.warn(
71+
`rate limited by ${graphqlUrl}, trying again in ${rateLimitRetryDelayMs / 1000} seconds at ${new Date(Date.now() + rateLimitRetryDelayMs).toLocaleTimeString()}`,
72+
)
73+
await new Promise((resolve) => setTimeout(resolve, rateLimitRetryDelayMs))
74+
}
75+
76+
const {
77+
data: { transactions },
78+
} = await response.json()
79+
80+
edges.push(...transactions.edges)
81+
82+
hasNextPage = transactions.pageInfo.hasNextPage
83+
}
84+
85+
return Object.fromEntries(
86+
edges.map(({ node: { id, tags } }) => [
87+
id,
88+
Object.fromEntries(
89+
tags.map(({ name, value }) => [
90+
namespace && name.startsWith(`${namespace}-`) ? name.slice(namespace.length + 1) : name,
91+
value,
92+
]),
93+
),
94+
]),
95+
)
96+
}
97+
98+
export async function fetchItem(
99+
id: string,
100+
rateLimitRetryDelayMs = defaults.rateLimitRetryDelayMs,
101+
arweaveUrl = defaults.arweaveUrl,
102+
): Promise<Response> {
103+
while (true) {
104+
const response = await fetch(`${arweaveUrl}/${id}`, { redirect: 'follow' })
105+
if (response.status !== 429) {
106+
return response
107+
}
108+
console.warn(
109+
`rate limited by ${arweaveUrl}, trying again in ${rateLimitRetryDelayMs / 1000} seconds at ${new Date(Date.now() + rateLimitRetryDelayMs).toLocaleTimeString()}`,
110+
)
111+
await new Promise((resolve) => setTimeout(resolve, rateLimitRetryDelayMs))
112+
}
113+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Config, Context, GenericTree, Payload, Signature } from '@0xsequence/wallet-primitives'
2+
import { Address, Hex } from 'ox'
3+
import { Reader as ReaderInterface } from '../index.js'
4+
import { defaults, Options } from './arweave.js'
5+
6+
export class Reader implements ReaderInterface {
7+
constructor(private readonly options: Options = defaults) {
8+
}
9+
10+
getConfiguration(imageHash: Hex.Hex): Promise<Config.Config | undefined> {
11+
}
12+
13+
getDeploy(wallet: Address.Address): Promise<{ imageHash: Hex.Hex; context: Context.Context } | undefined> {
14+
}
15+
16+
getWallets(signer: Address.Address): Promise<{ [wallet: Address.Address]: { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSignerLeaf } }> {
17+
}
18+
19+
getWalletsForSapient(signer: Address.Address, imageHash: Hex.Hex): Promise<{ [wallet: Address.Address]: { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSapientSignerLeaf } }> {
20+
}
21+
22+
getWitnessFor(wallet: Address.Address, signer: Address.Address): Promise<{ chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSignerLeaf } | undefined> {
23+
}
24+
25+
getWitnessForSapient(wallet: Address.Address, signer: Address.Address, imageHash: Hex.Hex): Promise<{ chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSapientSignerLeaf } | undefined> {
26+
}
27+
28+
getConfigurationUpdates(wallet: Address.Address, fromImageHash: Hex.Hex, options?: { allUpdates?: boolean }): Promise<Array<{ imageHash: Hex.Hex; signature: Signature.RawSignature }>> {
29+
}
30+
31+
getTree(imageHash: Hex.Hex): Promise<GenericTree.Tree | undefined> {
32+
}
33+
34+
getPayload(digest: Hex.Hex): Promise<{ chainId: number; payload: Payload.Parented; wallet: Address.Address } | undefined> {
35+
}
36+
}

0 commit comments

Comments
 (0)