USPD Mint & Burn Integration Guide
This guide provides a comprehensive technical walkthrough for integrating USPD minting and burning functionality into your application using Node.js and viem.
Overview
USPD is a yield-bearing stablecoin built on Ethereum. When you mint USPD:
- You send ETH to the contract
- Your ETH is automatically staked with Lido to generate staking rewards
- You receive USPD tokens (redeemable 1:1 with USD value)
- The backing collateral becomes stETH, which accrues yield over time
When you burn USPD:
- You burn your USPD tokens
- You receive stETH collateral back (proportional to your USPD amount)
- Optionally, you can swap the stETH to ETH in a single transaction via Uniswap
Architecture
USPD uses a dual-token system:
- USPD: ERC-20 token representing your balance (view layer)
- cUSPD: Shares contract that holds the actual yield-accruing shares
The USPD token is a view on top of cUSPD shares. As the yield factor increases (from stETH staking rewards), your USPD balance grows automatically without any transactions.
Contract Addresses
You can fetch the latest contract addresses programmatically:
// Fetch all deployments
const response = await fetch('https://uspd.io/api/deployments');
const deployments = await response.json();
// Find mainnet deployment
const mainnet = deployments.find(d => d.chainId === 1);
const uspdTokenAddress = mainnet?.deployment.contracts.uspdToken;
const burnSwapAddress = mainnet?.deployment.contracts.uspdBurnSwap;For reference, the mainnet addresses are:
- USPD Token:
0x476ef9ac6D8673E220d0E8BC0a810C2Dc6A2AA84 - UspdBurnSwap: Check the API for the latest address
Prerequisites
Install the required packages:
npm install viem dotenvCreate a .env file:
PRIVATE_KEY=your_private_key_here
RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEYStep 1: Fetch Oracle Price Data
USPD uses a signed price oracle to ensure accurate ETH/USD pricing. Before minting or burning, you must fetch the latest price data:
import { createWalletClient, createPublicClient, http, parseEther, formatUnits } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import dotenv from 'dotenv';
dotenv.config();
// Fetch current ETH/USD price with signature
async function fetchPriceData() {
const response = await fetch('https://uspd.io/api/v1/price/eth-usd');
if (!response.ok) {
throw new Error(`Failed to fetch price: ${response.status}`);
}
const data = await response.json();
return {
price: BigInt(data.price),
decimals: data.decimals,
dataTimestamp: BigInt(data.dataTimestamp),
requestTimestamp: BigInt(data.requestTimestamp),
assetPair: data.assetPair,
signature: data.signature
};
}The price API returns:
- price: ETH price in USD with 18 decimals (e.g., 3000.50 = 3000500000000000000000)
- decimals: Always 18 for precision
- dataTimestamp: When the price was recorded
- requestTimestamp: When the request was made
- assetPair: Keccak256 hash of “MORPHER:ETH_USD”
- signature: EIP-712 signature from the oracle signer
Step 2: Minting USPD
Here’s a complete example of minting USPD by sending ETH:
// USPD Token ABI (mint function only)
const uspdTokenAbi = [
{
inputs: [
{ name: 'recipient', type: 'address' },
{
name: 'priceQuery',
type: 'tuple',
components: [
{ name: 'assetPair', type: 'bytes32' },
{ name: 'price', type: 'uint256' },
{ name: 'decimals', type: 'uint8' },
{ name: 'dataTimestamp', type: 'uint256' },
{ name: 'signature', type: 'bytes' }
]
}
],
name: 'mint',
outputs: [],
stateMutability: 'payable',
type: 'function'
},
{
inputs: [{ name: 'account', type: 'address' }],
name: 'balanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
];
async function mintUSPD(ethAmount) {
// Setup clients
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const publicClient = createPublicClient({
chain: mainnet,
transport: http(process.env.RPC_URL)
});
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(process.env.RPC_URL)
});
// Fetch contract addresses
const response = await fetch('https://uspd.io/api/deployments');
const deployments = await response.json();
const mainnetDeployment = deployments.find(d => d.chainId === 1);
const uspdTokenAddress = mainnetDeployment.deployment.contracts.uspdToken;
console.log(`Minting USPD with ${ethAmount} ETH...`);
// Step 1: Fetch fresh price data
const priceData = await fetchPriceData();
// Calculate expected USPD output
const ethPriceUsd = Number(priceData.price) / (10 ** priceData.decimals);
const expectedUspd = ethAmount * ethPriceUsd;
console.log(`ETH Price: $${ethPriceUsd.toFixed(2)}`);
console.log(`Expected USPD: ~${expectedUspd.toFixed(2)}`);
// Step 2: Check mintable capacity (optional but recommended)
const capacityResponse = await fetch('https://uspd.io/api/v1/system/mintable-capacity');
const capacity = await capacityResponse.json();
const maxMintableEth = Number(formatUnits(BigInt(capacity.totalMintableEth), 18));
if (ethAmount > maxMintableEth) {
throw new Error(`Requested ${ethAmount} ETH exceeds system capacity of ${maxMintableEth} ETH`);
}
// Step 3: Prepare price query struct
const priceQuery = {
assetPair: priceData.assetPair,
price: priceData.price,
decimals: priceData.decimals,
dataTimestamp: priceData.dataTimestamp,
signature: priceData.signature
};
// Step 4: Call mint function
const hash = await walletClient.writeContract({
address: uspdTokenAddress,
abi: uspdTokenAbi,
functionName: 'mint',
args: [account.address, priceQuery],
value: parseEther(ethAmount.toString()),
gas: 3000000n // Adjust gas as needed
});
console.log(`Transaction submitted: ${hash}`);
// Step 5: Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log(`Transaction confirmed in block ${receipt.blockNumber}`);
// Step 6: Check new USPD balance
const balance = await publicClient.readContract({
address: uspdTokenAddress,
abi: uspdTokenAbi,
functionName: 'balanceOf',
args: [account.address]
});
console.log(`New USPD balance: ${formatUnits(balance, 18)}`);
return { hash, receipt, balance };
}
// Example: Mint 1 ETH worth of USPD
mintUSPD(1.0)
.then(() => console.log('Mint completed successfully'))
.catch(err => console.error('Mint failed:', err));Step 3: Burning USPD to stETH
Burn your USPD tokens to receive stETH collateral:
const uspdBurnAbi = [
{
inputs: [
{ name: 'uspdAmount', type: 'uint256' },
{
name: 'priceQuery',
type: 'tuple',
components: [
{ name: 'assetPair', type: 'bytes32' },
{ name: 'price', type: 'uint256' },
{ name: 'decimals', type: 'uint8' },
{ name: 'dataTimestamp', type: 'uint256' },
{ name: 'signature', type: 'bytes' }
]
}
],
name: 'burn',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
},
{
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
{ name: 'v', type: 'uint8' },
{ name: 'r', type: 'bytes32' },
{ name: 's', type: 'bytes32' }
],
name: 'permit',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
}
];
async function burnUSPD(uspdAmount) {
// Setup clients
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const publicClient = createPublicClient({
chain: mainnet,
transport: http(process.env.RPC_URL)
});
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(process.env.RPC_URL)
});
// Fetch contract address
const response = await fetch('https://uspd.io/api/deployments');
const deployments = await response.json();
const mainnetDeployment = deployments.find(d => d.chainId === 1);
const uspdTokenAddress = mainnetDeployment.deployment.contracts.uspdToken;
console.log(`Burning ${uspdAmount} USPD...`);
// Step 1: Fetch fresh price data
const priceData = await fetchPriceData();
// Calculate expected stETH output
const ethPriceUsd = Number(priceData.price) / (10 ** priceData.decimals);
const expectedStEth = uspdAmount / ethPriceUsd;
console.log(`ETH Price: $${ethPriceUsd.toFixed(2)}`);
console.log(`Expected stETH: ~${expectedStEth.toFixed(4)}`);
// Step 2: Prepare price query
const priceQuery = {
assetPair: priceData.assetPair,
price: priceData.price,
decimals: priceData.decimals,
dataTimestamp: priceData.dataTimestamp,
signature: priceData.signature
};
// Step 3: Call burn function
const hash = await walletClient.writeContract({
address: uspdTokenAddress,
abi: uspdBurnAbi,
functionName: 'burn',
args: [parseEther(uspdAmount.toString()), priceQuery],
gas: 4000000n
});
console.log(`Transaction submitted: ${hash}`);
// Step 4: Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log(`Transaction confirmed in block ${receipt.blockNumber}`);
return { hash, receipt };
}
// Example: Burn 100 USPD
burnUSPD(100)
.then(() => console.log('Burn completed successfully'))
.catch(err => console.error('Burn failed:', err));Step 4: Burn and Swap to ETH (Advanced)
For a seamless experience, use the UspdBurnSwap contract to burn USPD and automatically swap the resulting stETH to ETH via Uniswap:
const burnSwapAbi = [
{
inputs: [
{ name: 'uspdAmount', type: 'uint256' },
{ name: 'minEthOut', type: 'uint256' },
{
name: 'priceQuery',
type: 'tuple',
components: [
{ name: 'assetPair', type: 'bytes32' },
{ name: 'price', type: 'uint256' },
{ name: 'decimals', type: 'uint8' },
{ name: 'dataTimestamp', type: 'uint256' },
{ name: 'signature', type: 'bytes' }
]
},
{ name: 'deadline', type: 'uint256' },
{ name: 'v', type: 'uint8' },
{ name: 'r', type: 'bytes32' },
{ name: 's', type: 'bytes32' }
],
name: 'burnAndSwapToEthWithPermit',
outputs: [{ name: 'ethReceived', type: 'uint256' }],
stateMutability: 'nonpayable',
type: 'function'
}
];
async function burnAndSwapToETH(uspdAmount, slippagePercent = 1.5) {
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const publicClient = createPublicClient({
chain: mainnet,
transport: http(process.env.RPC_URL)
});
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(process.env.RPC_URL)
});
// Fetch contract addresses
const response = await fetch('https://uspd.io/api/deployments');
const deployments = await response.json();
const mainnetDeployment = deployments.find(d => d.chainId === 1);
const uspdTokenAddress = mainnetDeployment.deployment.contracts.uspdToken;
const burnSwapAddress = mainnetDeployment.deployment.contracts.uspdBurnSwap;
console.log(`Burning ${uspdAmount} USPD and swapping to ETH...`);
// Step 1: Fetch price data
const priceData = await fetchPriceData();
// Calculate expected ETH output with slippage
const ethPriceUsd = Number(priceData.price) / (10 ** priceData.decimals);
const expectedEth = uspdAmount / ethPriceUsd;
const multiStepBuffer = 0.5; // Additional buffer for multi-step swap
const totalSlippage = slippagePercent + multiStepBuffer;
const minEthOut = parseEther((expectedEth * (1 - totalSlippage / 100)).toFixed(18));
console.log(`Expected ETH: ~${expectedEth.toFixed(4)}`);
console.log(`Min ETH (with ${totalSlippage}% slippage): ${formatUnits(minEthOut, 18)}`);
// Step 2: Sign EIP-2612 Permit (gasless approval)
const deadline = BigInt(Math.floor(Date.now() / 1000) + 20 * 60); // 20 min
// Get nonce for permit
const nonce = await publicClient.readContract({
address: uspdTokenAddress,
abi: [
{
inputs: [{ name: 'owner', type: 'address' }],
name: 'nonces',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
],
functionName: 'nonces',
args: [account.address]
});
const domain = {
name: 'USPD',
version: '1',
chainId: 1,
verifyingContract: uspdTokenAddress
};
const types = {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
};
const message = {
owner: account.address,
spender: burnSwapAddress,
value: parseEther(uspdAmount.toString()),
nonce: nonce,
deadline: deadline
};
const signature = await walletClient.signTypedData({
account,
domain,
types,
primaryType: 'Permit',
message
});
// Split signature
const r = signature.slice(0, 66);
const s = '0x' + signature.slice(66, 130);
const v = parseInt(signature.slice(130, 132), 16);
// Step 3: Prepare price query
const priceQuery = {
assetPair: priceData.assetPair,
price: priceData.price,
decimals: priceData.decimals,
dataTimestamp: priceData.dataTimestamp,
signature: priceData.signature
};
// Step 4: Call burnAndSwapToEthWithPermit
const hash = await walletClient.writeContract({
address: burnSwapAddress,
abi: burnSwapAbi,
functionName: 'burnAndSwapToEthWithPermit',
args: [
parseEther(uspdAmount.toString()),
minEthOut,
priceQuery,
deadline,
v,
r,
s
],
gas: 5000000n
});
console.log(`Transaction submitted: ${hash}`);
// Step 5: Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log(`Transaction confirmed in block ${receipt.blockNumber}`);
console.log('Successfully burned USPD and received ETH!');
return { hash, receipt };
}
// Example: Burn 100 USPD and swap to ETH with 1.5% slippage
burnAndSwapToETH(100, 1.5)
.then(() => console.log('Burn and swap completed successfully'))
.catch(err => console.error('Burn and swap failed:', err));API Endpoints Reference
Get ETH/USD Price
GET https://uspd.io/api/v1/price/eth-usdResponse:
{
"price": "3000500000000000000000",
"decimals": 18,
"dataTimestamp": 1701234567890,
"requestTimestamp": 1701234567890,
"assetPair": "0x...",
"signature": "0x..."
}Get Mintable Capacity
GET https://uspd.io/api/v1/system/mintable-capacityResponse:
{
"totalMintableEth": "1000000000000000000",
"mintableUspdValue": "3000500000000000000000"
}Get Contract Deployments
GET https://uspd.io/api/deploymentsResponse:
[
{
"chainId": 1,
"deployment": {
"contracts": {
"uspdToken": "0x476ef9ac6D8673E220d0E8BC0a810C2Dc6A2AA84",
"uspdBurnSwap": "0x...",
"oracle": "0x...",
"stabilizer": "0x..."
},
"config": {
"stETHAddress": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"
}
}
}
]Error Handling
Common errors and solutions:
“Price data too old”
- The oracle signature has expired. Fetch fresh price data before retrying.
“Insufficient collateral”
- The system doesn’t have enough collateral to support the mint. Check mintable capacity first.
“Too little received” (Uniswap)
- Increase your slippage tolerance or try again when market conditions improve.
“Permit expired”
- The permit deadline has passed. Generate a new permit signature.
Best Practices
- Always fetch fresh price data immediately before minting/burning
- Check mintable capacity before large mints to avoid failures
- Use appropriate gas limits:
- Mint: ~3,000,000 gas
- Burn to stETH: ~4,000,000 gas
- Burn and swap to ETH: ~5,000,000 gas
- Set reasonable slippage for swaps (1.5-2% for volatile markets)
- Use permit signatures for gasless approvals when possible
- Validate inputs before submitting transactions
Complete Working Example
Save this as uspd-integration.js:
import { createWalletClient, createPublicClient, http, parseEther, formatUnits } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import dotenv from 'dotenv';
dotenv.config();
// [Include all the code from the sections above]
async function main() {
try {
// Mint 0.1 ETH worth of USPD
console.log('=== MINTING USPD ===');
await mintUSPD(0.1);
console.log('\n=== WAITING 5 SECONDS ===\n');
await new Promise(resolve => setTimeout(resolve, 5000));
// Burn 100 USPD back to stETH
console.log('=== BURNING USPD TO stETH ===');
await burnUSPD(100);
console.log('\n=== ALL OPERATIONS COMPLETE ===');
} catch (error) {
console.error('Error:', error);
process.exit(1);
}
}
main();Run with:
node uspd-integration.jsFurther Resources
Support
For technical support and questions:
- Discord: Join our community
- GitHub Issues: Report bugs