Skip to main content

Quick start for developers

You want to integrate SealedIP into your own app, script, or tool. This page covers the monorepo layout, the contract test suite, and the SDK entry points you will use most.

Repo layout

contracts/ Foundry project: SealedAuction + AuctionRevealCondition
sdk/ TypeScript SDK: bid placement, sealing reserve, settlement, types
web/ Next.js reference UI (treat as a worked example, not a dependency)
docs-site/ This Docusaurus site

Contracts: build and test

cd contracts
forge build
forge test

The test suite has 43 Foundry tests covering the full auction lifecycle, reserve sealing, CDR reveal conditions, and settlement edge cases.

Deploy gotcha on Aeneid

Aeneid reports a near-zero EIP-1559 base fee. A forge script --broadcast without a pinned gas price sends transactions at ~15 wei that never mine. You must pass these flags:

forge script script/YourScript.s.sol \
--rpc-url https://aeneid.storyrpc.io \
--broadcast \
--legacy \
--gas-price 100000000000

Neither vm.txGasPrice in Solidity nor gas_price in foundry.toml is honored by the script broadcaster on this network.

Install the SDK

npm install @sealedip/sdk viem

viem is a peer dependency. The SDK ships TypeScript types; no @types package is needed.

WASM constraint

encryptBid, decryptBid, placeBid, sealReserve, and settleAuction all load @piplabs/cdr-crypto WASM. They run in Node only, not in the browser or edge runtimes. The reference web app proxies bid placement through a Next.js API route.

SDK entry points

Read auction state

read.ts
import {getAuction, getAllBids} from '@sealedip/sdk/auction'

const auction = await getAuction(3n)
console.log(auction.state, auction.bidCount, auction.reserveHasCiphertext)

const bids = await getAllBids(3n)
bids.forEach((b, i) => console.log(i, b.ciphertextUuid, b.hasCiphertext))

getAuction returns an Auction object with reserveUuid: number and reserveHasCiphertext: boolean. There is no reservePrice field; the reserve is sealed until settlement.

Place a sealed bid (Node / API route)

bid.ts
import {placeBid} from '@sealedip/sdk/bidder'
import {parseEther} from 'viem'

const result = await placeBid({
auctionId: 3n,
amount: parseEther('2.5'),
bidderPrivateKey: process.env.BIDDER_KEY as `0x${string}`,
rpcUrl: 'https://aeneid.storyrpc.io',
})

console.log(`Sealed bid: uuid=${result.uuid} tx=${result.submitTxHash}`)

placeBid runs: approve WIP, allocate bid slot, sign + encode payload, TDH2-encrypt, submit ciphertext. Four on-chain transactions.

Seal the reserve (seller, Node / API route)

seal-reserve.ts
import {sealReserve} from '@sealedip/sdk'
import {parseEther} from 'viem'

const result = await sealReserve({
auctionId: 3n,
reserve: parseEther('1.0'),
sellerPrivateKey: process.env.SELLER_KEY as `0x${string}`,
rpcUrl: 'https://aeneid.storyrpc.io',
})

console.log(`Reserve sealed: tx=${result.submitTxHash}`)

This is optional. If the seller never calls sealReserve, the contract defaults the floor to 0.

Settle an auction (orchestrator, Node)

settle.ts
import {settleAuction} from '@sealedip/sdk/orchestrator'

const result = await settleAuction({
auctionId: 3n,
orchestratorPrivateKey: process.env.ORCHESTRATOR_KEY as `0x${string}`,
rpcUrl: 'https://aeneid.storyrpc.io',
})

console.log(`Settled: tx=${result.settleTxHash}`)

settleAuction waits for the deadline, triggers the auction, decrypts every sealed bid via CDR, decrypts the sealed reserve (if any), and calls settle(id, reveals, reserveReveal). All of it in one helper. See Settling for the step-by-step breakdown.

Raw ABI access

If you prefer calling the contracts directly:

import {sealedAuctionAbi} from '@sealedip/sdk/abi/sealed-auction'
import {SEALED_AUCTION_ADDRESS, GAS_PRICE_WEI} from '@sealedip/sdk/constants'
import {createPublicClient, http} from 'viem'
import {storyAeneid} from 'viem/chains'

const client = createPublicClient({
chain: storyAeneid,
transport: http('https://aeneid.storyrpc.io'),
})

const [seller, ipId, licenseTermsId, reserveUuid, reserveHasCiphertext,
deadline, state, bidCount] = await client.readContract({
address: SEALED_AUCTION_ADDRESS,
abi: sealedAuctionAbi,
functionName: 'auctions',
args: [3n],
})

For write calls on Aeneid, always pass gasPrice: GAS_PRICE_WEI (the EIP-1559 base fee is effectively zero, so legacy pricing is required).

Deployed addresses

import {
SEALED_AUCTION_ADDRESS, // '0xb41B87f2E58E3f6139d6B6D2323C7AdFd47FC9ee'
AUCTION_REVEAL_CONDITION_ADDRESS, // '0x1A3cCd475CCFb47D1353f62F3bca6DEfAC3D69bC'
WIP_ADDRESS, // '0x1514000000000000000000000000000000000000'
GAS_PRICE_WEI, // 100_000_000_000n
} from '@sealedip/sdk/constants'

Canonical list is sdk/src/constants.ts. All addresses are on Story Aeneid testnet (chain id 1315).

Listen for settlement events

watch.ts
import {sealedAuctionAbi} from '@sealedip/sdk/abi/sealed-auction'
import {SEALED_AUCTION_ADDRESS} from '@sealedip/sdk/constants'
import {createPublicClient, http} from 'viem'
import {storyAeneid} from 'viem/chains'

const client = createPublicClient({
chain: storyAeneid,
transport: http('https://aeneid.storyrpc.io'),
})

const unwatch = client.watchContractEvent({
address: SEALED_AUCTION_ADDRESS,
abi: sealedAuctionAbi,
eventName: 'AuctionSettled',
onLogs: logs => {
for (const log of logs) {
console.log(
`Auction #${log.args.auctionId} settled:`,
`winner=${log.args.winner}`,
`amount=${log.args.winningAmount}`,
`licenseTokenId=${log.args.licenseTokenId}`,
)
}
},
})
// later: unwatch()

Next steps