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
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)
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)
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)
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
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
- SDK types —
Auction,Bid,BidReveal,ReserveReveal, and more - Encrypting a bid — the TDH2 layer in detail
- Submitting a bid —
placeBidandsealReserve - Settling —
settleAuctionorchestrator flow