A Swift package for building Cardano transactions with advanced UTxO selection algorithms and comprehensive script support. This library provides a high-level transaction builder that handles UTxO selection, fee calculation, change computation, and transaction validation.
- 🏗️ Fluent Builder API: Easy-to-use method chaining for transaction construction
- 🎯 Smart UTxO Selection: Multiple coin selection algorithms (CIP-0002 compliant)
- 🪙 Multi-Asset Support: Native support for Cardano native tokens
- 📜 Script Integration: Full support for Plutus and Native scripts
- 💰 Automatic Fee Calculation: Smart fee estimation including script execution costs
- 🔄 Change Handling: Intelligent change computation with minimum ADA requirements
- ⚡ Execution Unit Estimation: Automatic estimation for Plutus script execution
- 🔒 Collateral Management: Automatic collateral selection and return handling
- iOS 14.0+
- macOS 14.0+
- watchOS 7.0+
- tvOS 14.0+
Add this to your Package.swift:
dependencies: [
.package(url: "https://github.com/Kingpin-Apps/swift-cardano-txbuilder.git", from: "0.1.0"),
]Note: The examples in this README use BlockFrostChainContext for real blockchain interactions. You'll need a BlockFrost API key from blockfrost.io to run the examples.
Or add it through Xcode:
- File → Add Package Dependencies
- Enter:
https://github.com/Kingpin-Apps/swift-cardano-txbuilder.git
Addresses are fundamental to Cardano transactions. Here's how to work with them:
import SwiftCardanoCore
// Create address from bech32 string
let paymentAddress = try Address(from: .string("addr_test1vr..."))
// Create stake address from stake verification key
let stakeVKey = try StakeVerificationKey.load(from: "path/to/stake.vkey")
let stakeAddress = try Address(
stakingPart: .verificationKeyHash(try stakeVKey.hash()),
network: .testnet
)
// Create base address (payment + staking credentials)
let paymentVKey = try PaymentVerificationKey.load(from: "path/to/payment.vkey")
let baseAddress = try Address(
paymentPart: .verificationKeyHash(try paymentVKey.hash()),
stakingPart: .verificationKeyHash(try stakeVKey.hash()),
network: .testnet
)
// Convert address to bech32 string
let bech32 = try paymentAddress.toBech32()
print("Address: \(bech32)")The library uses a SigningKeyType enum to wrap different key types:
import SwiftCardanoCore
// Load normal signing keys
let paymentSKey = try PaymentSigningKey.load(from: "path/to/payment.skey")
let stakeSKey = try StakeSigningKey.load(from: "path/to/stake.skey")
// Wrap in SigningKeyType for use with transaction helpers
let signingKeys: [SigningKeyType] = [
.signingKey(paymentSKey),
.signingKey(stakeSKey)
]
// For extended signing keys (HD wallets)
let extendedKey = try PaymentExtendedSigningKey.load(from: "path/to/payment.xskey")
let extendedSigningKeys: [SigningKeyType] = [
.extendedSigningKey(extendedKey)
]Note: Keys loaded from files are typically 32 bytes (normal keys) or 128 bytes (extended keys). The library handles both automatically.
import SwiftCardanoTxBuilder
import SwiftCardanoCore
import SwiftCardanoChain
// Initialize BlockFrost chain context
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
// Initialize transaction builder
let txBuilder = TxBuilder(context: chainContext)
// Define addresses
let senderAddress = try Address(from: .string("addr_test1vr..."))
let receiverAddress = try Address(from: .string("addr_test1vq..."))
// Build a simple ADA transfer transaction
let txBody = try await txBuilder
.addInputAddress(.address(senderAddress)) // Source address
.addOutput(TransactionOutput(
address: receiverAddress, // Destination
amount: Value(coin: 2_000_000) // 2 ADA
))
.build(changeAddress: senderAddress)
print("Transaction built with fee: \(txBody.fee)")// Initialize BlockFrost chain context for multi-asset transactions
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
let txBuilder = TxBuilder(context: chainContext)
// Define token policy and name
let tokenPolicyId = ScriptHash(payload: "1f847bb9ac60e869780037c0510dbd89f745316db7ec4fee81ff1e97".hexStringToData)
let tokenName = AssetName(from: "MyToken")
// Define addresses
let vaultAddress = try Address(from: .string("addr_test1vrs324jltsc0ssuptpa5ngpfk89cps92xa99a2t6vlg6kdqtm5qnv"))
let receiverAddress = try Address(from: .string("addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x"))
let txBody = try await txBuilder
.addInputAddress(.address(vaultAddress)) // Address containing tokens
.addOutput(TransactionOutput(
address: receiverAddress,
amount: Value(
coin: 1_500_000, // 1.5 ADA
multiAsset: MultiAsset([
tokenPolicyId: Asset([tokenName: 100]) // 100 tokens
])
)
))
.build(changeAddress: vaultAddress, mergeChange: true) // Merge change back to vault// Initialize BlockFrost chain context for script transactions
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
let txBuilder = TxBuilder(context: chainContext)
// Create Plutus script and addresses
let plutusScript = PlutusV2Script(data: Data("your script bytes here".utf8))
let scriptHash = try plutusScriptHash(script: .plutusV2Script(plutusScript))
let scriptAddress = try Address(
paymentPart: .scriptHash(scriptHash),
stakingPart: .none,
network: chainContext.network
)
// Create datum and redeemer
let datum = PlutusData()
let redeemer = Redeemer(
data: PlutusData(),
exUnits: ExecutionUnits(mem: 1_000_000, steps: 1_000_000)
)
// Create script UTxO
let scriptUtxo = UTxO(
input: try TransactionInput(from: .list([
.string("18cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef"),
.int(0)
])),
output: TransactionOutput(
address: scriptAddress,
amount: Value(coin: 10_000_000),
datumHash: try datum.hash()
)
)
let txBody = try await txBuilder
.addScriptInput(
scriptUtxo,
script: .script(.plutusV2Script(plutusScript)),
datum: .plutusData(datum),
redeemer: redeemer
)
.addOutput(TransactionOutput(
address: receiverAddress,
amount: Value(coin: 5_000_000)
))
.build(changeAddress: receiverAddress)// Initialize BlockFrost chain context for minting
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
let txBuilder = TxBuilder(context: chainContext)
// Create minting script
let plutusScript = PlutusV2Script(data: Data("minting script bytes".utf8))
let scriptHash = try plutusScriptHash(script: .plutusV2Script(plutusScript))
// Set up minting MultiAsset
txBuilder.mint = try MultiAsset(from: [
scriptHash.payload.toHex: ["NewToken": 1000]
])
// Create redeemer for minting
let mintRedeemer = Redeemer(
data: PlutusData(),
exUnits: ExecutionUnits(mem: 1_000_000, steps: 1_000_000)
)
// Add the minting script
try txBuilder.addMintingScript(
.script(.plutusV2Script(plutusScript)),
redeemer: mintRedeemer
)
// Add input address for fees
txBuilder.addInputAddress(.address(senderAddress))
// Add output with minted tokens
try txBuilder.addOutput(
TransactionOutput(
address: receiverAddress,
amount: Value(
coin: 2_000_000,
multiAsset: txBuilder.mint!
)
)
)
let txBody = try await txBuilder.build(changeAddress: senderAddress)The main transaction builder class that orchestrates transaction construction:
public class TxBuilderCommon initialization patterns:
// For simple ADA transactions
let txBuilder = TxBuilder(context: chainContext)
// For Plutus script transactions
let txBuilder = TxBuilder(context: chainContext)
// With custom UTxO selectors
let txBuilder = TxBuilder(
context: chainContext,
utxoSelectors: [
RandomImproveMultiAsset(), // Primary
LargestFirstSelector() // Fallback
]
)The library includes multiple UTxO selection strategies:
CIP-0002 compliant random-improve algorithm optimized for multi-asset transactions.
Simple largest-first selection algorithm.
// Initialize BlockFrost context
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
// Configure with custom selectors
let txBuilder = TxBuilder(
context: chainContext,
utxoSelectors: [
RandomImproveMultiAsset(), // Primary
LargestFirstSelector() // Fallback
]
)The library works with different chain context implementations. For production use with BlockFrost:
import SwiftCardanoChain
// Initialize with API key from environment variable
let chainContext = try await BlockFrostChainContext(
network: .mainnet, // or .preview, .preprod
environmentVariable: "BLOCKFROST_API_KEY"
)
// Or initialize with direct API key
let chainContext = try await BlockFrostChainContext(
projectId: "your-blockfrost-project-id",
network: .mainnet
)
// For script transactions, use the appropriate redeemer type
let scriptChainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)For testing, you can use the provided mock context:
// Mock context for testing (from test suite)
let mockContext = MockChainContext()
mockContext._utxos = [/* your test UTxOs */]
let txBuilder = TxBuilder(context: mockContext)// Initialize with custom configuration
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
let txBuilder = TxBuilder(
context: chainContext,
executionMemoryBuffer: 0.2, // 20% memory buffer
executionStepBuffer: 0.2, // 20% step buffer
feeBuffer: 100_000, // Additional 0.1 ADA fee buffer
ttl: 123456789, // Time to live
collateralReturnThreshold: 5_000_000 // 5 ADA threshold
)
// Or configure after initialization
txBuilder.ttl = 123456789
txBuilder.feeBuffer = 50_000
txBuilder.executionMemoryBuffer = 0.15The builder automatically handles:
- Transaction size-based fees
- Script execution unit estimation
- Collateral calculation for script transactions
- Minimum UTxO requirements
// Initialize chain context and builder
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
let txBuilder = TxBuilder(context: chainContext)
// Handle errors during transaction building
do {
let senderAddress = try Address(from: .string("addr_test1vr..."))
let txBody = try await txBuilder
.addInputAddress(.address(senderAddress))
.addOutput(TransactionOutput(
address: try Address(from: .string("addr_test1vq...")),
amount: Value(coin: 2_000_000)
))
.build(changeAddress: senderAddress)
print("Transaction built successfully with fee: \(txBody.fee)")
} catch CardanoTxBuilderError.utxoSelectionFailed(let message) {
print("UTxO selection failed: \(message)")
} catch CardanoTxBuilderError.insufficientBalance(let message) {
print("Insufficient balance: \(message)")
} catch CardanoTxBuilderError.transactionTooLarge(let message) {
print("Transaction too large: \(message)")
} catch CardanoTxBuilderError.invalidInput(let message) {
print("Invalid input: \(message)")
} catch {
print("Unexpected error: \(error)")
}utxoSelectionFailed: Not enough UTxOs to cover outputs and feesinsufficientBalance: Insufficient funds for the transactiontransactionTooLarge: Transaction exceeds protocol limitsinvalidInput: Invalid script, address, or datum validation
The TxBuilder is generic over a redeemer type T. Choose the appropriate type based on your transaction requirements:
Use Never when your transaction doesn't involve any Plutus scripts:
// For simple ADA transfers, token transfers without scripts
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
let txBuilder = TxBuilder(context: chainContext)
// These transactions don't require redeemers:
// - Basic ADA transfers
// - Native token transfers (using existing tokens)
// - Certificate transactions (stake registration, delegation)
// - Transactions using only native scriptsUse PlutusData when working with Plutus scripts:
// For transactions involving Plutus scripts
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
let txBuilder = TxBuilder(context: chainContext)
// These transactions require PlutusData redeemers:
// - Spending from Plutus script addresses
// - Minting tokens with Plutus minting policies
// - Certificate transactions with Plutus scripts
// - Withdrawal transactions with Plutus scriptsFor strongly-typed redeemers, define custom types that conform to CBORSerializable & Hashable:
// Define your custom redeemer type
struct MyRedeemer: CBORSerializable, Hashable {
let action: String
let value: Int
// Implement CBORSerializable methods
func toCBOR() -> CBOR {
return .array([.string(action), .int(value)])
}
init(from cbor: CBOR) throws {
// Deserialize from CBOR
// Implementation details...
}
}
// Use with TxBuilder
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
let txBuilder = TxBuilder(context: chainContext)Once you've built a transaction, you need to sign it and submit it to the blockchain:
// Build the transaction to get the body
let transactionBody = try await txBuilder.build(changeAddress: senderAddress)
// Create a transaction with the body (witness set will be empty initially)
let unsignedTransaction = Transaction(
transactionBody: transactionBody,
transactionWitnessSet: try txBuilder.buildWitnessSet(),
auxiliaryData: nil
)Before submitting, you need to sign the transaction with the appropriate private keys:
// Option 1: Build and sign in one step
let paymentSigningKey = try PaymentSigningKey.load(from: "path/to/payment.skey")
let signedTransaction = try await txBuilder.buildAndSign(
signingKeys: [.signingKey(paymentSigningKey)],
changeAddress: senderAddress
)
// Option 2: Manually sign a pre-built transaction
var witnessSet = try txBuilder.buildWitnessSet()
var vkeyWitnesses = [] as [VerificationKeyWitness]
let vkey: any VerificationKeyProtocol = try paymentSigningKey.toVerificationKey()
let vkeyType: VerificationKeyType = try paymentSigningKey.toVerificationKeyType()
let signature = try paymentSigningKey.sign(
data: transactionBody.hash()
)
vkeyWitnesses.append(
VerificationKeyWitness(
vkey: vkeyType,
signature: signature
)
)
witnessSet.vkeyWitnesses = .nonEmptyOrderedSet(
NonEmptyOrderedSet(vkeyWitnesses)
)
let signedTransaction = Transaction(
transactionBody: transactionBody,
transactionWitnessSet: witnessSet,
auxiliaryData: nil
)Use the chain context to submit your signed transaction:
do {
// Submit the signed transaction
let txId = try await chainContext.submitTx(tx: .transaction(signedTransaction))
print("Transaction submitted successfully!")
print("Transaction ID: \(txId)")
} catch {
print("Failed to submit transaction: \(error)")
}The chain context supports multiple submission formats:
// Submit as transaction object
let txId = try await chainContext.submitTx(tx: .transaction(signedTransaction))
// Submit as raw CBOR bytes
let cborData = signedTransaction.toCBORData()
let txId = try await chainContext.submitTx(tx: .bytes(cborData))
// Submit as hex string
let hexString = cborData.toHex
let txId = try await chainContext.submitTx(tx: .string(hexString))
// Or use the lower-level CBOR method directly
let txId = try await chainContext.submitTxCBOR(cbor: cborData)// 1. Initialize chain context and builder
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
let txBuilder = TxBuilder(context: chainContext)
let paymentSigningKey = try PaymentSigningKey.load(from: "path/to/payment.skey")
// 2. Build and sign transaction
let signedTransaction = try await txBuilder
.addInputAddress(.address(senderAddress))
.addOutput(TransactionOutput(
address: receiverAddress,
amount: Value(coin: 2_000_000)
))
.buildAndSign(
signingKeys: [.signingKey(paymentSigningKey)],
changeAddress: senderAddress
)
// 3. Submit to blockchain
do {
let txId = try await chainContext.submitTx(tx: .transaction(signedTransaction))
print("Success! Transaction ID: \(txId)")
// Optional: Monitor transaction confirmation
// You can query the transaction status using the txId
} catch {
print("Submission failed: \(error)")
}do {
let txId = try await chainContext.submitTx(tx: .transaction(signedTransaction))
print("Transaction submitted: \(txId)")
} catch ChainContextError.invalidArgument(let message) {
print("Invalid transaction: \(message)")
// Transaction is malformed or violates protocol rules
} catch ChainContextError.transactionFailed(let message) {
print("Submission failed: \(message)")
// Network issues or node rejection
} catch {
print("Unexpected error: \(error)")
}Register a stake address on the blockchain to begin participating in staking:
import SwiftCardanoTxBuilder
import SwiftCardanoCore
import SwiftCardanoChain
// Initialize chain context
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
// Load keys
let stakeVKey = try StakeVerificationKey.load(from: "path/to/stake.vkey")
let paymentSKey = try PaymentSigningKey.load(from: "path/to/payment.skey")
let stakeSKey = try StakeSigningKey.load(from: "path/to/stake.skey")
// Define fee payment address
let feePaymentAddress = try Address(from: .string("addr_test1vr..."))
// Create transaction builder
let txBuilder = TxBuilder(context: chainContext)
// Build and sign the registration transaction
let tx = try await txBuilder.transactions.stakeAddressRegistration(
stakeVerificationKey: stakeVKey,
feePaymentAddress: feePaymentAddress,
signingKeys: [
.signingKey(paymentSKey),
.signingKey(stakeSKey)
]
)
// Submit the transaction
let txId = try await chainContext.submitTx(tx: .transaction(tx))
print("Stake address registered! Transaction ID: \(txId)")Note: The helper automatically:
- Verifies the stake address is not already registered
- Creates the
StakeRegistrationcertificate - Gathers UTxOs from the fee payment address
- Calculates fees including the registration deposit
Delegate a registered stake address to a stake pool:
import SwiftCardanoTxBuilder
import SwiftCardanoCore
import SwiftCardanoChain
// Initialize chain context
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
// Load keys
let stakeVKey = try StakeVerificationKey.load(from: "path/to/stake.vkey")
let paymentSKey = try PaymentSigningKey.load(from: "path/to/payment.skey")
let stakeSKey = try StakeSigningKey.load(from: "path/to/stake.skey")
// Define the stake pool to delegate to
let poolKeyHash = try PoolKeyHash(from: .string("pool1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpp3c6l"))
let poolOperator = PoolOperator(poolKeyHash: poolKeyHash)
// Define fee payment address
let feePaymentAddress = try Address(from: .string("addr_test1vr..."))
// Create transaction builder
let txBuilder = TxBuilder(context: chainContext)
// Build and sign the delegation transaction
let tx = try await txBuilder.transactions.stakeDelegation(
stakeVerificationKey: stakeVKey,
poolOperator: poolOperator,
feePaymentAddress: feePaymentAddress,
signingKeys: [
.signingKey(paymentSKey),
.signingKey(stakeSKey)
]
)
// Submit the transaction
let txId = try await chainContext.submitTx(tx: .transaction(tx))
print("Stake delegated! Transaction ID: \(txId)")Note: The helper automatically:
- Verifies the stake address is registered
- Creates the
StakeDelegationcertificate - Handles fee calculation and change
Withdraw accumulated staking rewards from a stake address:
import SwiftCardanoTxBuilder
import SwiftCardanoCore
import SwiftCardanoChain
// Initialize chain context
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
// Load keys
let stakeVKey = try StakeVerificationKey.load(from: "path/to/stake.vkey")
let paymentSKey = try PaymentSigningKey.load(from: "path/to/payment.skey")
let stakeSKey = try StakeSigningKey.load(from: "path/to/stake.skey")
// Define addresses
let feePaymentAddress = try Address(from: .string("addr_test1vr..."))
let receiverAddress = try Address(from: .string("addr_test1vq..."))
// Create transaction builder
let txBuilder = TxBuilder(context: chainContext)
// Withdraw rewards to a specific address
let tx = try await txBuilder.transactions.withdrawRewards(
from: stakeVKey,
to: receiverAddress, // Optional: omit to merge with change
feePaymentAddress: feePaymentAddress,
signingKeys: [
.signingKey(paymentSKey),
.signingKey(stakeSKey)
]
)
// Submit the transaction
let txId = try await chainContext.submitTx(tx: .transaction(tx))
print("Rewards withdrawn! Transaction ID: \(txId)")Note: The helper automatically:
- Queries the stake address for available rewards
- Creates the withdrawal with the full reward balance
- If
toaddress is provided, sends rewards there - If
toisnil, rewards are merged with change to fee payment address
Delegate voting power to a Delegated Representative (DRep) for on-chain governance (CIP-1694):
import SwiftCardanoTxBuilder
import SwiftCardanoCore
import SwiftCardanoChain
// Initialize chain context
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
// Load keys
let stakeVKey = try StakeVerificationKey.load(from: "path/to/stake.vkey")
let paymentSKey = try PaymentSigningKey.load(from: "path/to/payment.skey")
let stakeSKey = try StakeSigningKey.load(from: "path/to/stake.skey")
// Define the DRep to delegate voting to
let drepCredential = DRepCredential(
credential: .verificationKeyHash(
try VerificationKeyHash(from: .string("drep1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq7e0l"))
)
)
let drep = try DRep(credential: DRepType(from: drepCredential))
// Define fee payment address
let feePaymentAddress = try Address(from: .string("addr_test1vr..."))
// Create transaction builder
let txBuilder = TxBuilder(context: chainContext)
// Build and sign the vote delegation transaction
let tx = try await txBuilder.transactions.voteDelegation(
stakeVerificationKey: stakeVKey,
drep: drep,
feePaymentAddress: feePaymentAddress,
signingKeys: [
.signingKey(paymentSKey),
.signingKey(stakeSKey)
]
)
// Submit the transaction
let txId = try await chainContext.submitTx(tx: .transaction(tx))
print("Vote delegated! Transaction ID: \(txId)")Note: Vote delegation allows you to participate in Cardano governance by delegating your voting power to a DRep who will vote on governance proposals on your behalf.
For new stake addresses, you can register and delegate in a single transaction:
import SwiftCardanoTxBuilder
import SwiftCardanoCore
import SwiftCardanoChain
// Initialize chain context
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
// Load keys
let stakeVKey = try StakeVerificationKey.load(from: "path/to/stake.vkey")
let paymentSKey = try PaymentSigningKey.load(from: "path/to/payment.skey")
let stakeSKey = try StakeSigningKey.load(from: "path/to/stake.skey")
// Define the stake pool to delegate to
let poolKeyHash = try PoolKeyHash(from: .string("pool1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpp3c6l"))
let poolOperator = PoolOperator(poolKeyHash: poolKeyHash)
// Define fee payment address
let feePaymentAddress = try Address(from: .string("addr_test1vr..."))
// Create transaction builder
let txBuilder = TxBuilder(context: chainContext)
// Build and sign the combined registration and delegation transaction
let tx = try await txBuilder.transactions.stakeAddressRegistrationAndDelegation(
stakeVerificationKey: stakeVKey,
poolOperator: poolOperator,
feePaymentAddress: feePaymentAddress,
signingKeys: [
.signingKey(paymentSKey),
.signingKey(stakeSKey)
]
)
// Submit the transaction
let txId = try await chainContext.submitTx(tx: .transaction(tx))
print("Stake address registered and delegated! Transaction ID: \(txId)")Note: This is more efficient than separate transactions and saves on transaction fees. Both payment and stake signing keys are required.
// Initialize chain context
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
let txBuilder = TxBuilder(context: chainContext)
// Define pool parameters
let poolParams = PoolParams(
poolOperator: PoolKeyHash(payload: Data(repeating: 0x31, count: POOL_KEY_HASH_SIZE)),
vrfKeyHash: VrfKeyHash(payload: Data(repeating: 0x31, count: VRF_KEY_HASH_SIZE)),
pledge: 100_000_000_000, // 100k ADA
cost: 340_000_000, // 340 ADA
margin: UnitInterval(numerator: 1, denominator: 50), // 2%
rewardAccount: RewardAccountHash(payload: Data(repeating: 0x31, count: REWARD_ACCOUNT_HASH_SIZE)),
poolOwners: .list([
VerificationKeyHash(payload: Data(repeating: 0x31, count: VERIFICATION_KEY_HASH_SIZE))
]),
relays: [
.singleHostAddr(SingleHostAddr(
port: 3001,
ipv4: IPv4Address("192.168.0.1")!,
ipv6: IPv6Address("::1")!
)),
.singleHostName(SingleHostName(
port: 3001,
dnsName: "relay1.example.com"
))
],
poolMetadata: try PoolMetadata(
url: try Url("https://meta1.example.com"),
poolMetadataHash: PoolMetadataHash(payload: Data(repeating: 0x31, count: POOL_METADATA_HASH_SIZE))
)
)
// Create pool registration certificate
let poolRegistration = PoolRegistration(poolParams: poolParams)
// Add input UTxO with sufficient funds
let ownerAddress = try Address(from: .string("addr_test1vr..."))
let poolUtxo = UTxO(
input: try TransactionInput(from: .list([
.bytes(Data(repeating: 0x32, count: 32)),
.int(2)
])),
output: TransactionOutput(
address: ownerAddress,
amount: Value(coin: 505_000_000) // 505 ADA
)
)
// Configure transaction
txBuilder.addInput(poolUtxo)
txBuilder.initialStakePoolRegistration = true
txBuilder.certificates = [.poolRegistration(poolRegistration)]
let txBody = try await txBuilder.build(changeAddress: ownerAddress)// Initialize chain context
let chainContext = try await BlockFrostChainContext(
network: .preview,
environmentVariable: "BLOCKFROST_API_KEY"
)
let txBuilder = TxBuilder(context: chainContext)
// Define governance action and voter
let poolKeyHash = PoolKeyHash(payload: Data(repeating: 0x31, count: POOL_KEY_HASH_SIZE))
let govActionId = GovActionID(
transactionId: try TransactionId(from: .string("41cb004bec7051621b19b46aea28f0657a586a05ce2013152ea9b9f1a5614cc7")),
govActionIndex: 0
)
// Optional vote anchor for metadata
let voteAnchor = Anchor(
anchorUrl: try Url("https://vote-metadata.example.com"),
anchorDataHash: AnchorDataHash(payload: Data(repeating: 0x42, count: 32))
)
// Add vote to transaction
txBuilder.addVote(
voter: .stakePoolKeyHash(poolKeyHash),
govActionId: govActionId,
vote: .yes,
anchor: voteAnchor
)
// Add funding input and build
let voterAddress = try Address(from: .string("addr_test1vr..."))
txBuilder.addInputAddress(.address(voterAddress))
let txBody = try await txBuilder.build(changeAddress: voterAddress)- SwiftCardanoCore: Core Cardano types and CBOR encoding
- SwiftCardanoChain: Chain context and network protocol abstractions
- SwiftNcal: Cryptographic functions
- Input Collection: Gather UTxOs from addresses or explicit inputs
- UTxO Selection: Apply selection algorithms to meet output requirements
- Script Handling: Process Plutus/Native scripts with redeemers and datums
- Fee Estimation: Calculate transaction fees including script execution costs
- Change Calculation: Compute change outputs with minimum ADA requirements
- Validation: Ensure transaction meets protocol constraints
- Builder Pattern: Fluent API with method chaining
- Strategy Pattern: Pluggable UTxO selection algorithms
- Generic Context: Abstraction for different network environments
- Protocol-Oriented: Extensible design with clear interfaces
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
git clone https://github.com/Kingpin-Apps/swift-cardano-txbuilder.git
cd swift-cardano-txbuilder
swift package resolve
swift build
swift testThis project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ for the Cardano ecosystem