Beginner10 min read
Edit on GitHubTypeScript / JavaScript
Build Cardano applications with Lucid and MeshJS using Nacho API
TypeScript is the most popular language for Cardano dApp development. This guide shows you how to integrate Nacho API with the two leading libraries: Lucid and MeshJS.
Lucid
Lucid is a lightweight, modular library for building Cardano transactions. It's ideal for applications that need fine-grained control over transaction construction.
Installation
npm install lucid-cardano
# or
pnpm add lucid-cardanoCreate a Custom Provider
Lucid uses "providers" to interact with the blockchain. Here's a custom provider for Nacho API:
import { Lucid, Provider, UTxO, ProtocolParameters, Credential, Address } from 'lucid-cardano'
class NachoProvider implements Provider {
private apiKey: string
private baseUrl = 'https://api.nacho.builders/v1/ogmios'
constructor(apiKey: string) {
this.apiKey = apiKey
}
private async rpc(method: string, params?: object) {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
body: JSON.stringify({
jsonrpc: '2.0',
method,
params,
id: Date.now()
})
})
const data = await response.json()
if (data.error) throw new Error(data.error.message)
return data.result
}
async getProtocolParameters(): Promise<ProtocolParameters> {
const result = await this.rpc('queryLedgerState/protocolParameters')
// Map Ogmios format to Lucid format
return {
minFeeA: result.minFeeCoefficient,
minFeeB: result.minFeeConstant.ada.lovelace,
maxTxSize: result.maxTransactionSize.bytes,
maxValSize: result.maxValueSize.bytes,
keyDeposit: BigInt(result.stakeCredentialDeposit.ada.lovelace),
poolDeposit: BigInt(result.stakePoolDeposit.ada.lovelace),
priceMem: result.scriptExecutionPrices.memory,
priceStep: result.scriptExecutionPrices.cpu,
maxTxExMem: BigInt(result.maxExecutionUnitsPerTransaction.memory),
maxTxExSteps: BigInt(result.maxExecutionUnitsPerTransaction.cpu),
coinsPerUtxoByte: BigInt(result.minUtxoDepositCoefficient),
collateralPercentage: result.collateralPercentage,
maxCollateralInputs: result.maxCollateralInputs,
costModels: result.plutusCostModels
}
}
async getUtxos(addressOrCredential: Address | Credential): Promise<UTxO[]> {
const address = typeof addressOrCredential === 'string'
? addressOrCredential
: addressOrCredential.hash // Handle credential lookup
const result = await this.rpc('queryLedgerState/utxo', {
addresses: [address]
})
return result.map(this.mapUtxo)
}
async getUtxosWithUnit(address: Address, unit: string): Promise<UTxO[]> {
const utxos = await this.getUtxos(address)
return utxos.filter(utxo =>
Object.keys(utxo.assets).some(asset => asset === unit)
)
}
async getUtxoByUnit(unit: string): Promise<UTxO> {
throw new Error('getUtxoByUnit requires indexer - use GraphQL endpoint')
}
async getUtxosByOutRef(outRefs: Array<{ txHash: string; outputIndex: number }>): Promise<UTxO[]> {
const result = await this.rpc('queryLedgerState/utxo', {
outputReferences: outRefs.map(ref => ({
transaction: { id: ref.txHash },
index: ref.outputIndex
}))
})
return result.map(this.mapUtxo)
}
async getDelegation(rewardAddress: string): Promise<{
poolId: string | null
rewards: bigint
}> {
// Query reward account state
const result = await this.rpc('queryLedgerState/rewardAccountSummaries', {
keys: [rewardAddress]
})
const account = result[rewardAddress]
return {
poolId: account?.delegate?.id || null,
rewards: BigInt(account?.rewards?.ada?.lovelace || 0)
}
}
async getDatum(datumHash: string): Promise<string> {
throw new Error('getDatum requires indexer - use GraphQL endpoint')
}
async awaitTx(txHash: string, checkInterval?: number): Promise<boolean> {
const interval = checkInterval || 3000
// Poll until transaction is confirmed
for (let i = 0; i < 60; i++) { // 3 minute timeout
try {
// Try to find UTxOs from this transaction
const tip = await this.rpc('queryNetwork/tip')
// Transaction is confirmed when it appears in UTxO set
// For now, just wait for blocks to pass
await new Promise(r => setTimeout(r, interval))
} catch {
// Continue polling
}
}
return true // Simplified - real implementation should check UTxOs
}
async submitTx(tx: string): Promise<string> {
const result = await this.rpc('submitTransaction', {
transaction: { cbor: tx }
})
return result.transaction.id
}
private mapUtxo(ogmiosUtxo: any): UTxO {
const txHash = ogmiosUtxo.transaction.id
const outputIndex = ogmiosUtxo.index
// Map assets
const assets: Record<string, bigint> = {
lovelace: BigInt(ogmiosUtxo.value.ada.lovelace)
}
// Add native assets
if (ogmiosUtxo.value.assets) {
for (const [policyId, tokens] of Object.entries(ogmiosUtxo.value.assets)) {
for (const [tokenName, amount] of Object.entries(tokens as Record<string, number>)) {
const unit = policyId + tokenName
assets[unit] = BigInt(amount)
}
}
}
return {
txHash,
outputIndex,
address: ogmiosUtxo.address,
assets,
datum: ogmiosUtxo.datum,
datumHash: ogmiosUtxo.datumHash,
scriptRef: ogmiosUtxo.script
}
}
}Initialize Lucid with Nacho
import { Lucid } from 'lucid-cardano'
async function initLucid() {
const provider = new NachoProvider(process.env.NACHO_API_KEY!)
const lucid = await Lucid.new(
provider,
'Mainnet' // or 'Preprod'
)
return lucid
}Build and Submit a Transaction
async function sendAda(
lucid: Lucid,
toAddress: string,
amount: bigint
) {
// Select wallet (from seed phrase, private key, or browser wallet)
lucid.selectWalletFromSeed('your seed phrase...')
// Build transaction
const tx = await lucid
.newTx()
.payToAddress(toAddress, { lovelace: amount })
.complete()
// Sign
const signedTx = await tx.sign().complete()
// Submit via Nacho API
const txHash = await signedTx.submit()
console.log('Transaction submitted:', txHash)
return txHash
}MeshJS
MeshJS is a full-featured SDK with React components, making it ideal for dApp frontends.
Installation
npm install @meshsdk/core @meshsdk/react
# or
pnpm add @meshsdk/core @meshsdk/reactCreate a Custom Provider
import { IFetcher, ISubmitter, IEvaluator, UTxO, Asset } from '@meshsdk/core'
class NachoMeshProvider implements IFetcher, ISubmitter, IEvaluator {
private apiKey: string
private baseUrl = 'https://api.nacho.builders/v1/ogmios'
constructor(apiKey: string) {
this.apiKey = apiKey
}
private async rpc(method: string, params?: object) {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
body: JSON.stringify({
jsonrpc: '2.0',
method,
params,
id: Date.now()
})
})
const data = await response.json()
if (data.error) throw new Error(data.error.message)
return data.result
}
// IFetcher implementation
async fetchAccountInfo(address: string) {
const result = await this.rpc('queryLedgerState/rewardAccountSummaries', {
keys: [address]
})
return result[address]
}
async fetchAddressUTxOs(address: string): Promise<UTxO[]> {
const result = await this.rpc('queryLedgerState/utxo', {
addresses: [address]
})
return result.map((utxo: any) => ({
input: {
txHash: utxo.transaction.id,
outputIndex: utxo.index
},
output: {
address: utxo.address,
amount: this.mapAssets(utxo.value),
dataHash: utxo.datumHash,
plutusData: utxo.datum,
scriptRef: utxo.script
}
}))
}
async fetchProtocolParameters() {
const result = await this.rpc('queryLedgerState/protocolParameters')
// Map to Mesh format
return {
epoch: 0, // Would need separate query
coinsPerUTxOSize: result.minUtxoDepositCoefficient.toString(),
priceMem: result.scriptExecutionPrices.memory,
priceStep: result.scriptExecutionPrices.cpu,
minFeeA: result.minFeeCoefficient,
minFeeB: result.minFeeConstant.ada.lovelace,
keyDeposit: result.stakeCredentialDeposit.ada.lovelace.toString(),
poolDeposit: result.stakePoolDeposit.ada.lovelace.toString(),
maxTxSize: result.maxTransactionSize.bytes,
maxValSize: result.maxValueSize.bytes.toString(),
collateralPercent: result.collateralPercentage,
maxCollateralInputs: result.maxCollateralInputs
}
}
// ISubmitter implementation
async submitTx(tx: string): Promise<string> {
const result = await this.rpc('submitTransaction', {
transaction: { cbor: tx }
})
return result.transaction.id
}
// IEvaluator implementation
async evaluateTx(tx: string): Promise<any> {
const result = await this.rpc('evaluateTransaction', {
transaction: { cbor: tx }
})
return result
}
private mapAssets(value: any): Asset[] {
const assets: Asset[] = [
{ unit: 'lovelace', quantity: value.ada.lovelace.toString() }
]
if (value.assets) {
for (const [policyId, tokens] of Object.entries(value.assets)) {
for (const [tokenName, amount] of Object.entries(tokens as Record<string, number>)) {
assets.push({
unit: policyId + tokenName,
quantity: amount.toString()
})
}
}
}
return assets
}
}Use with MeshJS
import { MeshTxBuilder, MeshWallet } from '@meshsdk/core'
async function sendWithMesh() {
const provider = new NachoMeshProvider(process.env.NACHO_API_KEY!)
// Create wallet from mnemonic
const wallet = new MeshWallet({
networkId: 1, // Mainnet
fetcher: provider,
submitter: provider,
key: {
type: 'mnemonic',
words: ['your', 'seed', 'phrase', '...']
}
})
// Build transaction
const txBuilder = new MeshTxBuilder({
fetcher: provider,
submitter: provider,
evaluator: provider
})
const unsignedTx = await txBuilder
.txOut('addr1...recipient', [{ unit: 'lovelace', quantity: '5000000' }])
.changeAddress(await wallet.getChangeAddress())
.selectUtxosFrom(await wallet.getUtxos())
.complete()
// Sign and submit
const signedTx = await wallet.signTx(unsignedTx)
const txHash = await provider.submitTx(signedTx)
console.log('Transaction submitted:', txHash)
}React Integration
MeshJS provides React hooks for wallet connection:
import { MeshProvider, CardanoWallet, useWallet } from '@meshsdk/react'
function App() {
return (
<MeshProvider>
<WalletConnect />
</MeshProvider>
)
}
function WalletConnect() {
const { connected, wallet } = useWallet()
const handleSend = async () => {
if (!wallet) return
const provider = new NachoMeshProvider(process.env.NEXT_PUBLIC_NACHO_API_KEY!)
const txBuilder = new MeshTxBuilder({
fetcher: provider,
submitter: provider
})
// Build with wallet UTxOs
const utxos = await wallet.getUtxos()
const changeAddress = await wallet.getChangeAddress()
const unsignedTx = await txBuilder
.txOut('addr1...', [{ unit: 'lovelace', quantity: '5000000' }])
.changeAddress(changeAddress)
.selectUtxosFrom(utxos)
.complete()
// Wallet handles signing
const signedTx = await wallet.signTx(unsignedTx)
const txHash = await provider.submitTx(signedTx)
alert(`Sent! TX: ${txHash}`)
}
return (
<div>
<CardanoWallet />
{connected && (
<button onClick={handleSend}>Send 5 ADA</button>
)}
</div>
)
}TypeScript Types
For better type safety, install the Ogmios types:
npm install @cardano-ogmios/schemaThen use them in your code:
import type {
Utxo,
ProtocolParameters,
Transaction
} from '@cardano-ogmios/schema'
// Now TypeScript knows the exact structure of API responsesBest Practices
- Reuse provider instances - Create one provider and share it across your app
- Handle errors gracefully - Wrap API calls in try/catch
- Use environment variables - Never hardcode API keys
- Test on preprod first - Use testnet before mainnet
- Implement retry logic - Network issues happen; be resilient
Next Steps
- Python Integration - For backend services
- Production Deployment - Best practices for going live
- Error Handling - Robust error management
Was this page helpful?