Commit 37a5b686 authored by Will Cory's avatar Will Cory

Add atst SDK

 add everything else

🎨 move the casting to the sdk

 clean up

Build sdk with tsup

Implement the package

remove the comment

Promise.all it

use readContracts

start finishing everyithing

finish sdk untested

add vite tests for the sdk

add all the reading tests

add rest of tests and functionality

last test

implementation is done

start on readmes

add todos

fix package versions

fix circleci

changeset

revert sdk change
parent bde2eaca
/**
* @internal
* Default data type for attestations
*/
export const DEFAULT_DATA_TYPE = 'string' as const
/**
* @internal
* Default RPC URL for Optimism
*/
export const DEFAULT_RPC_URL = 'https://mainnet.optimism.io'
// constants
export { ATTESTATION_STATION_ADDRESS } from './constants/attestationStationAddress'
// lib
export { readAttestation } from './lib/readAttestation'
export { readAttestations } from './lib/readAttestations'
export { prepareWriteAttestation } from './lib/prepareWriteAttestation'
export { writeAttestation } from './lib/writeAttestation'
export { abi } from './lib/abi'
export { parseAttestationBytes } from './lib/parseAttestationBytes'
// types
export type { AttestationReadParams } from './types/AttestationReadParams'
export type { WagmiBytes } from './types/WagmiBytes'
export type { DataTypeOption } from './types/DataTypeOption'
// Vitest Snapshot v1
exports[`logger > \${level}() > logs message "error" 1`] = `"error"`;
exports[`logger > \${level}() > logs message "info" 1`] = `"info"`;
exports[`logger > \${level}() > logs message "log" 1`] = `"log"`;
exports[`logger > \${level}() > logs message "success" 1`] = `"success"`;
exports[`logger > \${level}() > logs message "warn" 1`] = `"warn"`;
import { BigNumber } from 'ethers'
import { toUtf8Bytes } from 'ethers/lib/utils.js'
import { expect, describe, it } from 'vitest'
import { WagmiBytes } from '../types/WagmiBytes'
import { parseAttestationBytes } from './parseAttestationBytes'
describe(parseAttestationBytes.name, () => {
it('works for strings', () => {
const str = 'Hello World'
const bytes = BigNumber.from(toUtf8Bytes(str)).toHexString() as WagmiBytes
expect(parseAttestationBytes(bytes, 'string')).toBe(str)
})
it('works for numbers', () => {
const num = 123
const bytes = BigNumber.from(num).toHexString() as WagmiBytes
expect(parseAttestationBytes(bytes, 'number')).toBe(num.toString())
})
it('works for addresses', () => {
const addr = '0x1234567890123456789012345678901234567890'
const bytes = BigNumber.from(addr).toHexString() as WagmiBytes
expect(parseAttestationBytes(bytes, 'address')).toBe(addr)
})
it('works for booleans', () => {
const bytes = BigNumber.from(1).toHexString() as WagmiBytes
expect(parseAttestationBytes(bytes, 'bool')).toBe('true')
})
it('should work for raw bytes', () => {
const bytes = '0x420'
expect(parseAttestationBytes(bytes, 'bytes')).toBe(bytes)
})
it('should return raw bytes for invalid type', () => {
const bytes = '0x420'
// @ts-expect-error - this is a test for an error case
expect(parseAttestationBytes(bytes, 'foo')).toBe(bytes)
})
})
import { BigNumber } from 'ethers'
import { toUtf8String } from 'ethers/lib/utils.js'
import type { DataTypeOption } from '../types/DataTypeOption'
import * as logger from './logger'
import type { WagmiBytes } from '../types/WagmiBytes'
export const parseAttestationBytes = (
attestationBytes: WagmiBytes,
dataType: DataTypeOption
) => {
if (dataType === 'bytes') {
return attestationBytes
}
if (dataType === 'number') {
return BigNumber.from(attestationBytes).toString()
}
if (dataType === 'address') {
return BigNumber.from(attestationBytes).toHexString()
}
if (dataType === 'bool') {
return BigNumber.from(attestationBytes).gt(0) ? 'true' : 'false'
}
if (dataType === 'string') {
return attestationBytes && toUtf8String(attestationBytes)
}
logger.warn(`unrecognized dataType ${dataType satisfies never}`)
return attestationBytes
}
import { connect, createClient } from '@wagmi/core'
import { providers, Wallet } from 'ethers'
import { expect, describe, it, beforeAll } from 'vitest'
import { MockConnector } from '@wagmi/core/connectors/mock'
import { prepareWriteAttestation } from './prepareWriteAttestation'
import { readAttestation } from './readAttestation'
const creator = '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3'
const about = '0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5'
const key = 'optimist.base-uri'
const chainId = 10
const provider = new providers.JsonRpcProvider(
{
url: 'http://localhost:8545',
},
chainId
)
const wallet = Wallet.createRandom({ provider })
createClient({
provider,
})
beforeAll(async () => {
await connect({
connector: new MockConnector({
options: {
chainId,
signer: new Wallet(wallet.privateKey, provider),
},
}),
})
})
describe(prepareWriteAttestation.name, () => {
it('Should correctly prepare an attestation', async () => {
const result = await prepareWriteAttestation(about, key, 'hello world')
expect(result.address).toMatchInlineSnapshot(
'"0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77"'
)
expect(result.chainId).toMatchInlineSnapshot('10')
expect(result.functionName).toMatchInlineSnapshot('"attest"')
expect(result.mode).toMatchInlineSnapshot('"prepared"')
expect(result.request.gasLimit).toMatchInlineSnapshot(`
{
"hex": "0xd6c9",
"type": "BigNumber",
}
`)
})
it('should throw an error if key is longer than 32 bytes', async () => {
const dataType = 'string'
await expect(
readAttestation(
creator,
about,
'this is a key that is way longer than 32 bytes so this key should throw an error matching the inline snapshot',
dataType
)
).rejects.toThrowErrorMatchingInlineSnapshot(
'"Key is longer than the max length of 32 for attestation keys"'
)
})
})
import { Address, prepareWriteContract } from '@wagmi/core'
import { formatBytes32String } from 'ethers/lib/utils.js'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import { WagmiBytes } from '../types/WagmiBytes'
import { abi } from './abi'
import { stringifyAttestationBytes } from './stringifyAttestationBytes'
export const prepareWriteAttestation = async (
about: Address,
key: string,
value: string | WagmiBytes | number | boolean,
chainId = 10,
contractAddress: Address = ATTESTATION_STATION_ADDRESS
) => {
const formattedKey = formatBytes32String(key) as WagmiBytes
return prepareWriteContract({
address: contractAddress,
abi,
functionName: 'attest',
chainId,
args: [about, formattedKey, stringifyAttestationBytes(value) as WagmiBytes],
})
}
import { createClient } from '@wagmi/core'
import { providers } from 'ethers'
import { expect, describe, it } from 'vitest'
import { readAttestation } from './readAttestation'
const creator = '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3'
const about = '0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5'
const key = 'optimist.base-uri'
const dataType = 'string'
const provider = new providers.JsonRpcProvider({
url: 'http://localhost:8545',
})
createClient({
provider,
})
describe(readAttestation.name, () => {
it('should return the attestation from attestation station', async () => {
const result = await readAttestation(creator, about, key, dataType)
expect(result).toMatchInlineSnapshot(
'"https://assets.optimism.io/4a609661-6774-441f-9fdb-453fdbb89931-bucket/optimist-nft/attributes"'
)
})
it('should throw an error if key is longer than 32 bytes', async () => {
await expect(
readAttestation(
creator,
about,
'this is a key that is way longer than 32 bytes so this key should throw an error matching the inline snapshot',
dataType
)
).rejects.toThrowErrorMatchingInlineSnapshot(
'"Key is longer than the max length of 32 for attestation keys"'
)
})
})
import type { Address } from '@wagmi/core'
import { DEFAULT_DATA_TYPE } from '../constants/defaultDataType'
import type { DataTypeOption } from '../types/DataTypeOption'
import { readAttestations } from './readAttestations'
/**
* reads attestation from the attestation station contract
*
* @param attestationRead - the parameters for reading an attestation
* @returns attestation result
* @throws Error if key is longer than 32 bytes
* @example
* const attestation = await readAttestation(
* {
* creator: creatorAddress,
* about: aboutAddress,
* key: 'my_key',
* },
*/
export const readAttestation = async (
creator: Address,
about: Address,
key: string,
dataType: DataTypeOption = DEFAULT_DATA_TYPE,
contractAddress: Address = '0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77'
) => {
const [result] = await readAttestations({
creator,
about,
key,
dataType,
contractAddress,
})
return result
}
import { createClient } from '@wagmi/core'
import { providers } from 'ethers'
import { expect, describe, it } from 'vitest'
import { readAttestation } from './readAttestation'
import { readAttestations } from './readAttestations'
const creator = '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3'
const about = '0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5'
const key = 'optimist.base-uri'
const provider = new providers.JsonRpcProvider({
url: 'http://localhost:8545',
})
createClient({
provider,
})
describe(readAttestation.name, () => {
it('should return attestations from attestation station', async () => {
const dataType = 'string'
const result = await readAttestations(
{
creator,
about,
key,
dataType,
},
{
creator,
about,
key,
dataType: 'bool',
},
{
creator,
about,
key,
dataType: 'bytes',
},
{
creator,
about,
key,
dataType: 'number',
}
)
expect(result).toMatchInlineSnapshot(
`
[
"https://assets.optimism.io/4a609661-6774-441f-9fdb-453fdbb89931-bucket/optimist-nft/attributes",
"true",
"0x68747470733a2f2f6173736574732e6f7074696d69736d2e696f2f34613630393636312d363737342d343431662d396664622d3435336664626238393933312d6275636b65742f6f7074696d6973742d6e66742f61747472696275746573",
"9665973469795080068873111198635018086067645613429821071805084917303478255842407465257371959707311987533859075426222329066766033171696373249109388415320911537042272090516917683029511016473045453921068327933733922308146003731827",
]
`
)
})
})
import { readContracts } from '@wagmi/core'
import { formatBytes32String } from 'ethers/lib/utils.js'
import { ATTESTATION_STATION_ADDRESS } from '../constants/attestationStationAddress'
import type { AttestationReadParams } from '../types/AttestationReadParams'
import type { WagmiBytes } from '../types/WagmiBytes'
import { abi } from './abi'
import { parseAttestationBytes } from './parseAttestationBytes'
/**
* reads attestations from the attestation station contract
*
* @returns an array of attestation values
* @throws Error if key is longer than 32 bytes
* @example
* const attestations = await readAttestations(
* {
* creator: creatorAddress,
* about: aboutAddress,
* key: 'my_key',
* allowFailure: false,
* },
* {
* creator: creatorAddress2,
* about: aboutAddress2,
* key: 'my_key',
* dataType: 'number',
* contractAddress: '0x1234',
* allowFailure: false,
* },
* )
*/
export const readAttestations = async (
...attestationReads: Array<AttestationReadParams>
) => {
const calls = attestationReads.map((attestation) => {
const {
creator,
about,
key,
contractAddress = ATTESTATION_STATION_ADDRESS,
allowFailure = false,
} = attestation
if (key.length > 32) {
throw new Error(
'Key is longer than the max length of 32 for attestation keys'
)
}
return {
address: contractAddress,
abi,
functionName: 'attestations',
args: [creator, about, formatBytes32String(key) as WagmiBytes],
allowFailure,
} as const
})
const results = await readContracts({
contracts: calls,
})
return results.map((dataBytes, i) => {
const dataType = attestationReads[i].dataType ?? 'string'
return parseAttestationBytes(dataBytes, dataType)
})
}
import { Address } from '@wagmi/core'
import { BigNumber } from 'ethers'
import { isAddress, isHexString, toUtf8Bytes } from 'ethers/lib/utils.js'
import { WagmiBytes } from '../types/WagmiBytes'
export const stringifyAttestationBytes = (
bytes: WagmiBytes | string | Address | number | boolean
) => {
if (typeof bytes === 'number') {
return BigNumber.from(bytes).toHexString()
}
if (typeof bytes === 'boolean') {
return bytes ? '0x1' : '0x0'
}
if (isAddress(bytes)) {
return bytes
}
if (isHexString(bytes)) {
return bytes
}
if (typeof bytes === 'string') {
return toUtf8Bytes(bytes)
}
throw new Error(`unrecognized bytes type ${bytes satisfies never}`)
}
import { writeContract } from '@wagmi/core'
import { describe, expect, it } from 'vitest'
import { writeAttestation } from './writeAttestation'
describe(writeAttestation.name, () => {
it('rexports writeContract from @wagmi/core', () => {
expect(writeAttestation).toBe(writeContract)
})
})
import { writeContract } from '@wagmi/core'
export { prepareWriteAttestation } from './prepareWriteAttestation'
export { abi } from './abi'
/**
* Writes an attestation to the blockchain
* Same function as `writeContract` from @wagmi/core
* To use first use prepareWriteContract
*
* @example
* const config = await prepareAttestation(about, key, value)
* const tx = await writeAttestation(config)
*/
export const writeAttestation = writeContract
import { Address } from '@wagmi/core'
import { DataTypeOption } from './DataTypeOption'
/**
* The parameters for reading bulk attestations
*/
export interface AttestationReadParams {
creator: Address
about: Address
key: string
dataType?: DataTypeOption
contractAddress?: Address
allowFailure?: boolean
}
import { z } from 'zod'
/**
* Zod validator for the DataType type
* string | bytes | number | bool | address
*/
export const dataTypeOptionValidator = z
.union([
z.literal('string'),
z.literal('bytes'),
z.literal('number'),
z.literal('bool'),
z.literal('address'),
])
.optional()
.default('string').describe(`Zod validator for the DataType type
string | bytes | number | bool | address`)
/**
* Options for attestation data type
*/
export type DataTypeOption = z.infer<typeof dataTypeOptionValidator>
/**
* @internal
* WagmiBytes is a type that represents a hex string with a length of 32 bytes.
*/
export type WagmiBytes = `0x${string}`
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment