@permissionless-technologies/upd-sdk
v0.2.4
Published
Universal Private Dollar SDK — Non-rebasing stablecoin backed by stETH with stabilizer collateral management
Maintainers
Readme
UPD SDK — Universal Private Dollar
Integrate UPD minting and burning into your protocol. Non-rebasing stablecoin backed by stETH.
npm install @permissionless-technologies/upd-sdkQuick Start
import { createPublicClient, http, parseEther } from 'viem'
import { mainnet } from 'viem/chains'
import { createUPDClient, quoteMintUPD } from '@permissionless-technologies/upd-sdk'
// 1. Create a client
const publicClient = createPublicClient({ chain: mainnet, transport: http() })
const client = createUPDClient({ publicClient, chainId: 1 })
// 2. Read data (no wallet needed)
const balance = await client.getUPDBalance('0x...')
const supply = await client.getTotalSupply()
const { totalStEthCollateral, totalUPDSupply } = await client.getCollateralInfo()
// 3. Quote a mint (pure math, no blockchain call)
const expectedUPD = quoteMintUPD(parseEther('1'), 2000n * 10n**18n)
// → 2000000000000000000000n (2000 UPD for 1 ETH at $2000)Minting UPD
Send ETH → receive UPD (pegged to $1). The StabilizerNFT contract handles collateral allocation automatically.
import { createWalletClient, custom, parseEther } from 'viem'
import { createUPDClient, createMockAttestation } from '@permissionless-technologies/upd-sdk'
const walletClient = createWalletClient({ chain: mainnet, transport: custom(window.ethereum) })
const client = createUPDClient({ publicClient, walletClient, chainId: 1 })
// Get a price attestation (see Oracle section below)
const attestation = await fetchPriceFromYourOracle() // your implementation
// Mint UPD
const txHash = await client.mint({
to: userAddress,
ethAmount: parseEther('1'),
priceAttestation: attestation,
})Burning UPD
Send UPD → receive stETH back.
import { parseUnits } from 'viem'
const txHash = await client.burn({
updAmount: parseUnits('2000', 18),
priceAttestation: attestation,
})Quote Functions
Pure math — no blockchain calls needed. Use these to show users expected amounts before a transaction.
import { quoteMintUPD, quoteBurnUPD, collateralizationRatio } from '@permissionless-technologies/upd-sdk'
// How much UPD for 1 ETH at $2000?
quoteMintUPD(parseEther('1'), 2000n * 10n**18n) // → 2000 UPD
// How much stETH for 2000 UPD at $2000?
quoteBurnUPD(parseUnits('2000', 18), 2000n * 10n**18n) // → 1 stETH
// Calculate collateralization ratio
collateralizationRatio(parseEther('1.25'), 2000n * 10n**18n, parseUnits('2000', 18)) // → 12500 (125%)Oracle Integration
Minting and burning require a signed price attestation from an authorized oracle signer. The attestation format:
interface PriceAttestationQuery {
price: bigint // ETH/USD price, 18 decimals (e.g. 2000e18)
decimals: number // Always 18
dataTimestamp: bigint // Milliseconds since epoch
assetPair: `0x${string}` // keccak256("MORPHER:ETH_USD")
signature: `0x${string}` // ECDSA sig from authorized signer
}For testing/development, use the mock helper:
import { createMockAttestation } from '@permissionless-technologies/upd-sdk'
const attestation = createMockAttestation(2000n * 10n**18n)
// Works with MockPriceOracle on local Anvil — NOT for productionFor production, the oracle service signs keccak256(abi.encodePacked(price, decimals, timestamp, assetPair)) with an authorized key. The PriceOracle contract validates this against Chainlink + Uniswap V3 (±5% deviation check on mainnet).
Contract Addresses
Addresses are loaded per chain ID:
import { getDeployment, getContractAddress } from '@permissionless-technologies/upd-sdk'
const deployment = getDeployment(1) // mainnet (when deployed)
const updToken = getContractAddress(1, 'UPDToken')
const stabNFT = getContractAddress(1, 'StabilizerNFT')To add a new chain, create src/deployments/[chainId].json with contract addresses.
React Hooks
Optional wagmi-based hooks for frontends:
import { useUPD, useCollateral, useStabilizer } from '@permissionless-technologies/upd-sdk/react'
function Dashboard() {
const { balance, totalSupply, isLoading } = useUPD({ tokenAddress, userAddress })
const { totalStEthCollateral, totalUPDSupply } = useCollateral({ reporterAddress, tokenAddress })
const { positionEscrowAddress } = useStabilizer({ stabilizerNFTAddress, tokenId: 1n })
}Contracts
| Contract | Purpose | |---|---| | UPDToken | Non-rebasing ERC20 ($1 = 1 UPD). Mint/burn via StabilizerNFT. | | StabilizerNFT | Entry point for minting/burning. Manages collateral allocation. | | PositionEscrow | Holds stETH per stabilizer. Yield increases collateral ratio. | | StabilizerEscrow | Holds unallocated stETH per stabilizer. | | PriceOracle | Validates signed ETH/USD attestations (Chainlink + Uniswap V3). | | OvercollateralizationReporter | System-wide collateral ratio tracking. |
ABIs
import {
UPD_TOKEN_ABI, STABILIZER_NFT_ABI, PRICE_ORACLE_ABI,
POSITION_ESCROW_ABI, STABILIZER_ESCROW_ABI,
} from '@permissionless-technologies/upd-sdk/contracts'Subpath Exports
@permissionless-technologies/upd-sdk # Everything
@permissionless-technologies/upd-sdk/core # Client, types, quote, oracle
@permissionless-technologies/upd-sdk/contracts # ABIs only
@permissionless-technologies/upd-sdk/react # React hooks (wagmi)Development
npm install # Install deps
npm run build # Build SDK (ESM + CJS + types)
npm test # Run TypeScript tests (vitest)
npm run test:forge # Run Solidity tests (forge)
npm run generate:abis # Rebuild ABIs from contracts