Skip to content
35 changes: 35 additions & 0 deletions packages/seismic-viem/src/abis/directory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Abi, Address } from 'viem'

export const DIRECTORY_ADDRESS =
'0x1000000000000000000000000000000000000004' as Address

export const DirectoryAbi = [
{
inputs: [{ name: '_addr', type: 'address' }],
name: 'checkHasKey',
outputs: [{ type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'to', type: 'address' }],
name: 'keyHash',
outputs: [{ type: 'bytes32' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getKey',
outputs: [{ type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: '_key', type: 'suint256' }],
name: 'setKey',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const satisfies Abi
185 changes: 185 additions & 0 deletions packages/seismic-viem/src/abis/src20.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import type { Abi } from 'viem'

export const SRC20Abi = [
{
inputs: [
{
internalType: 'address',
name: 'owner',
type: 'address',
indexed: true,
},
{
internalType: 'address',
name: 'spender',
type: 'address',
indexed: true,
},
{
internalType: 'bytes32',
name: 'encryptKeyHash',
type: 'bytes32',
indexed: true,
},
{
internalType: 'bytes',
name: 'encryptedAmount',
type: 'bytes',
indexed: false,
},
],
type: 'event',
name: 'Approval',
anonymous: false,
},
{
inputs: [
{
internalType: 'address',
name: 'from',
type: 'address',
indexed: true,
},
{
internalType: 'address',
name: 'to',
type: 'address',
indexed: true,
},
{
internalType: 'bytes32',
name: 'encryptKeyHash',
type: 'bytes32',
indexed: true,
},
{
internalType: 'bytes',
name: 'encryptedAmount',
type: 'bytes',
indexed: false,
},
],
type: 'event',
name: 'Transfer',
anonymous: false,
},
{
inputs: [],
stateMutability: 'view',
type: 'function',
name: 'DOMAIN_SEPARATOR',
outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
},
{
inputs: [],
stateMutability: 'view',
type: 'function',
name: 'INTELLIGENCE_ADDRESS',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
},
{
inputs: [{ internalType: 'address', name: 'spender', type: 'address' }],
stateMutability: 'view',
type: 'function',
name: 'allowance',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
},
{
inputs: [
{ internalType: 'address', name: 'spender', type: 'address' },
{ internalType: 'suint256', name: 'amount', type: 'suint256' },
],
stateMutability: 'nonpayable',
type: 'function',
name: 'approve',
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
},
{
inputs: [],
stateMutability: 'view',
type: 'function',
name: 'balance',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
},
{
inputs: [],
stateMutability: 'view',
type: 'function',
name: 'decimals',
outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }],
},
{
inputs: [],
stateMutability: 'view',
type: 'function',
name: 'intelligence',
outputs: [
{
internalType: 'contract IIntelligence',
name: '',
type: 'address',
},
],
},
{
inputs: [],
stateMutability: 'view',
type: 'function',
name: 'name',
outputs: [{ internalType: 'string', name: '', type: 'string' }],
},
{
inputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
name: 'nonces',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
},
{
inputs: [
{ internalType: 'address', name: 'owner', type: 'address' },
{ internalType: 'address', name: 'spender', type: 'address' },
{ internalType: 'suint256', name: 'value', type: 'suint256' },
{
internalType: 'uint256',
name: 'deadline',
type: 'uint256',
},
{ internalType: 'uint8', name: 'v', type: 'uint8' },
{ internalType: 'bytes32', name: 'r', type: 'bytes32' },
{ internalType: 'bytes32', name: 's', type: 'bytes32' },
],
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
name: 'permit',
},
{
inputs: [],
stateMutability: 'view',
type: 'function',
name: 'symbol',
outputs: [{ internalType: 'string', name: '', type: 'string' }],
},
{
inputs: [
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'suint256', name: 'amount', type: 'suint256' },
],
stateMutability: 'nonpayable',
type: 'function',
name: 'transfer',
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
},
{
inputs: [
{ internalType: 'address', name: 'from', type: 'address' },
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'suint256', name: 'amount', type: 'suint256' },
],
stateMutability: 'nonpayable',
type: 'function',
name: 'transferFrom',
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
},
] as const satisfies Abi
18 changes: 18 additions & 0 deletions packages/seismic-viem/src/actions/src20/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Hex } from 'viem'

export const NONCE_LENGTH = 24 // 12 bytes in hex string

export function parseEncryptedData(encryptedData: Hex): {
ciphertext: Hex
nonce: Hex
} {
// Handle empty/zero encrypted data
if (!encryptedData || encryptedData === '0x' || encryptedData.length <= 2) {
throw new Error('Empty encrypted data - recipient has no key')
}

const nonce = `0x${encryptedData.slice(-NONCE_LENGTH)}` as Hex
const ciphertext = encryptedData.slice(0, -NONCE_LENGTH) as Hex

return { ciphertext, nonce }
}
74 changes: 74 additions & 0 deletions packages/seismic-viem/src/actions/src20/directory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { Address, Hex } from 'viem'
import { keccak256 } from 'viem'

import { DIRECTORY_ADDRESS } from '@sviem/abis/directory.ts'
import { DirectoryAbi } from '@sviem/abis/directory.ts'
import type { ShieldedWalletClient } from '@sviem/client.ts'
import { signedReadContract } from '@sviem/contract/read.ts'
import { shieldedWriteContract } from '@sviem/contract/write.ts'

const TX_TIMEOUT_MS = 30_000

function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
const timeout = new Promise<never>((_, reject) =>
setTimeout(
() => reject(new Error(`Transaction timed out after ${ms}ms`)),
ms
)
)
return Promise.race([promise, timeout])
}

export async function checkRegistration(
client: ShieldedWalletClient,
address: Address
): Promise<boolean> {
const hasKey = await client.readContract({
address: DIRECTORY_ADDRESS,
abi: DirectoryAbi,
functionName: 'checkHasKey',
args: [address],
})
return hasKey as boolean
}

export async function getKeyHash(
client: ShieldedWalletClient,
address: Address
): Promise<Hex> {
const keyHash = await client.readContract({
address: DIRECTORY_ADDRESS,
abi: DirectoryAbi,
functionName: 'keyHash',
args: [address],
})
return keyHash as Hex
}

export async function getKey(client: ShieldedWalletClient): Promise<Hex> {
const key = await signedReadContract(client, {
address: DIRECTORY_ADDRESS,
abi: DirectoryAbi,
functionName: 'getKey',
})
return ('0x' + (key as bigint).toString(16).padStart(64, '0')) as Hex // TODO: shift to different function
}

export async function registerKey(
client: ShieldedWalletClient,
aesKey: Hex
): Promise<Hex> {
const txPromise = shieldedWriteContract(client, {
chain: client.chain,
address: DIRECTORY_ADDRESS,
abi: DirectoryAbi,
functionName: 'setKey',
args: [BigInt(aesKey)],
})

return withTimeout(txPromise, TX_TIMEOUT_MS)
}

export function computeKeyHash(aesKey: Hex): Hex {
return keccak256(aesKey) as Hex
}
66 changes: 66 additions & 0 deletions packages/seismic-viem/src/actions/src20/src20Actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { Account, Chain, Hex, Transport } from 'viem'
import type { ShieldedPublicClient, ShieldedWalletClient } from '@sviem/client.ts'
import type { WatchSRC20EventsParams } from '@sviem/actions/src20/types.ts'
import { watchSRC20Events } from '@sviem/actions/src20/watchSRC20Events.ts'
import { watchSRC20EventsWithKey } from '@sviem/actions/src20/watchSRC20EventsWithKey.ts'

/** Actions for SRC20 on a public client */
export type SRC20PublicActions = {
watchSRC20EventsWithKey: (
viewingKey: Hex,
params: WatchSRC20EventsParams
) => Promise<() => void>
}

/** Actions for SRC20 on a wallet client */
export type SRC20WalletActions = {
watchSRC20Events: (params: WatchSRC20EventsParams) => Promise<() => void>
}

/**
* SRC20 actions for a public client.
*
* @example
* ```typescript
* const publicClient = createShieldedPublicClient(...)
* .extend(src20PublicActions)
*
* const unwatch = await publicClient.watchSRC20EventsWithKey(viewingKey, {
* address: '0x...',
* onTransfer: (log) => console.log(log),
* })
* ```
*/
export const src20PublicActions = <
TTransport extends Transport,
TChain extends Chain | undefined = Chain | undefined,
>(
client: ShieldedPublicClient<TTransport, TChain>
): SRC20PublicActions => ({
watchSRC20EventsWithKey: (viewingKey, params) =>
watchSRC20EventsWithKey(client as any, viewingKey, params),
})

/**
* SRC20 actions for a wallet client.
*
* @example
* ```typescript
* const walletClient = createShieldedWalletClient(...)
* .extend(src20WalletActions)
*
* const unwatch = await walletClient.watchSRC20Events({
* address: '0x...',
* onTransfer: (log) => console.log(log),
* })
* ```
*/
export const src20WalletActions = <
TTransport extends Transport,
TChain extends Chain | undefined = Chain | undefined,
TAccount extends Account = Account,
>(
client: ShieldedWalletClient<TTransport, TChain, TAccount>
): SRC20WalletActions => ({
watchSRC20Events: (params) => watchSRC20Events(client as any, params),
})
Loading
Loading