> For the complete documentation index, see [llms.txt](https://docs.ama.one/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ama.one/sdk/4.-transaction-building.md).

# 4. Transaction Building

Complete guide to building and signing transactions with the Amadeus Protocol SDK.

### Overview

Transactions in Amadeus Protocol are structured data that represent actions on the blockchain. They must be properly built, signed, and serialized before submission.

### TransactionBuilder Class

The `TransactionBuilder` class provides a convenient API for building and signing transactions.

#### Creating a Builder Instance

```typescript
import { TransactionBuilder } from '@amadeus-protocol/sdk'

// Create builder with private key (convenient for multiple transactions)
const builder = new TransactionBuilder('5Kd3N...') // Base58 encoded seed

// Or create without private key (must provide keys in each method call)
const builder = new TransactionBuilder()
```

### Building Transfer Transactions

#### Option 1: Build and Sign in One Step (Recommended)

```typescript
const builder = new TransactionBuilder(privateKey)

const { txHash, txPacked } = builder.transfer({
	recipient: '5Kd3N...', // Base58 encoded recipient address
	amount: 10.5, // Amount in human-readable format
	symbol: 'AMA' // Token symbol
})

// Submit the transaction
const result = await sdk.transaction.submit(txPacked)
```

#### Option 2: Build Unsigned, Then Sign (More Control)

```typescript
const builder = new TransactionBuilder(privateKey)

// Build unsigned transaction
const unsignedTx = builder.buildTransfer({
	recipient: '5Kd3N...',
	amount: 10.5,
	symbol: 'AMA'
})

// Can inspect or modify unsignedTx before signing
console.log('Nonce:', unsignedTx.tx.nonce)
console.log('Action:', unsignedTx.tx.action)

// Sign the transaction
const { txHash, txPacked } = builder.sign(unsignedTx)

// Submit the transaction
const result = await sdk.transaction.submit(txPacked)
```

### Building Custom Transactions

#### Option 1: Build and Sign in One Step

```typescript
import { TransactionBuilder, fromBase58, toAtomicAma } from '@amadeus-protocol/sdk'

const builder = new TransactionBuilder(privateKey)

const { txHash, txPacked } = builder.buildAndSign('Coin', 'transfer', [
	fromBase58('5Kd3N...'), // Recipient bytes
	toAtomicAma(10.5).toString(), // Amount in atomic units
	'AMA' // Token symbol
])
```

#### Option 2: Build Unsigned, Then Sign

```typescript
const builder = new TransactionBuilder(privateKey)

// Build unsigned transaction
const unsignedTx = builder.build('Coin', 'transfer', [
	fromBase58('5Kd3N...'),
	toAtomicAma(10.5).toString(),
	'AMA'
])

// Inspect or modify if needed
console.log('Transaction:', unsignedTx.tx)

// Sign the transaction
const { txHash, txPacked } = builder.sign(unsignedTx)
```

### Using Static Methods

You can also use static methods without creating an instance:

#### Build and Sign Transfer

```typescript
import { TransactionBuilder } from '@amadeus-protocol/sdk'

const { txHash, txPacked } = TransactionBuilder.buildSignedTransfer({
	senderPrivkey: '5Kd3N...', // Base58 encoded seed
	recipient: '5Kd3N...', // Base58 encoded recipient address
	amount: 10.5, // Amount in human-readable format
	symbol: 'AMA' // Token symbol
})
```

#### Build Unsigned, Then Sign

```typescript
import {
	TransactionBuilder,
	getPublicKey,
	deriveSkAndSeed64FromBase58Seed
} from '@amadeus-protocol/sdk'

// Derive keys from seed
const { seed64 } = deriveSkAndSeed64FromBase58Seed('5Kd3N...')
const signerPubKey = getPublicKey(seed64)

// Build unsigned transfer
const unsignedTx = TransactionBuilder.buildTransfer(
	{ recipient: '5Kd3N...', amount: 10.5, symbol: 'AMA' },
	signerPubKey
)

// Sign the transaction
const { txHash, txPacked } = TransactionBuilder.sign(unsignedTx, '5Kd3N...')
```

### Transaction Structure

#### Unsigned Transaction

```typescript
interface UnsignedTransaction {
	signer: Uint8Array // Sender's public key (48 bytes)
	nonce: bigint // Transaction nonce
	action: TransactionAction
}

interface TransactionAction {
	op: 'call' // Operation type
	contract: string // Contract name or address
	function: string // Function to call
	args: SerializableValue[] // Function arguments
}
```

#### Signed Transaction Result

```typescript
interface BuildTransactionResult {
	txHash: string // Transaction hash (Base58 encoded)
	txPacked: Uint8Array // Packed transaction ready for submission
}
```

### Transaction Nonces

Nonces are automatically generated using timestamps:

```typescript
// Nonce = BigInt(Date.now()) * 1_000_000n
// This ensures uniqueness for transactions
```

**Important**: For high-frequency transactions, ensure sufficient time between transactions to avoid nonce collisions.

### Amount Handling

Always use atomic units for transaction amounts:

```typescript
import { toAtomicAma } from '@amadeus-protocol/sdk'

// ✅ Good - use conversion function
const amount = toAtomicAma(10.5) // Returns 10500000000

// ❌ Bad - may lose precision
const amount = 10.5 * 1000000000
```

### Submitting Transactions

#### Submit Without Waiting

```typescript
const result = await sdk.transaction.submit(txPacked)

if (result.error === 'ok') {
	console.log('Transaction hash:', result.hash)
} else {
	console.error('Transaction error:', result.error)
}
```

#### Submit and Wait for Confirmation

```typescript
try {
	const result = await sdk.transaction.submitAndWait(txPacked)

	if (result.error === 'ok') {
		console.log('Transaction confirmed!')
		console.log('Hash:', result.hash)
		console.log('Entry hash:', result.metadata?.entry_hash)
		console.log('Receipt:', result.receipt)
	} else {
		console.error('Transaction error:', result.error)
	}
} catch (error) {
	console.error('Transaction failed or timed out:', error)
}
```

### Common Transaction Types

#### Token Transfer

```typescript
const builder = new TransactionBuilder(privateKey)

const { txHash, txPacked } = builder.transfer({
	recipient: recipientAddress,
	amount: amount,
	symbol: 'AMA'
})
```

#### Contract Call

```typescript
const builder = new TransactionBuilder(privateKey)

const { txHash, txPacked } = builder.buildAndSign('ContractName', 'functionName', [
	arg1,
	arg2,
	arg3
])
```

### Error Handling

```typescript
import { AmadeusSDKError } from '@amadeus-protocol/sdk'

try {
	const { txHash, txPacked } = builder.transfer({
		recipient: address,
		amount: 10.5,
		symbol: 'AMA'
	})

	const result = await sdk.transaction.submit(txPacked)

	if (result.error === 'ok') {
		console.log('Success:', result.hash)
	} else {
		console.error('Transaction error:', result.error)
	}
} catch (error) {
	if (error instanceof AmadeusSDKError) {
		console.error('SDK Error:', error.message)
	} else {
		console.error('Unexpected error:', error)
	}
}
```

### Best Practices

#### 1. Always Validate Inputs

```typescript
function validateAddress(address: string): boolean {
	try {
		const bytes = fromBase58(address)
		return bytes.length === 48
	} catch {
		return false
	}
}

if (!validateAddress(recipient)) {
	throw new Error('Invalid recipient address')
}
```

#### 2. Check Balance Before Transferring

```typescript
const balance = await sdk.wallet.getBalance(senderAddress, 'AMA')
if (balance.balance.float < amount + fee) {
	throw new Error('Insufficient balance')
}
```

#### 3. Use Appropriate Nonce Timing

For high-frequency transactions, add delays:

```typescript
async function submitMultipleTransactions(txs: Transaction[]) {
	for (const tx of txs) {
		await sdk.transaction.submit(tx)
		await new Promise((resolve) => setTimeout(resolve, 100)) // 100ms delay
	}
}
```

#### 4. Handle Transaction Errors

```typescript
const result = await sdk.transaction.submit(txPacked)

switch (result.error) {
	case 'ok':
		console.log('Success')
		break
	case 'insufficient_funds':
		console.error('Not enough balance')
		break
	case 'invalid_signature':
		console.error('Invalid signature')
		break
	default:
		console.error('Unknown error:', result.error)
}
```

### Advanced Usage

#### Building Transactions Without Private Key

```typescript
const builder = new TransactionBuilder()

// Must provide public key for building
const { seed64 } = deriveSkAndSeed64FromBase58Seed(privateKey)
const signerPubKey = getPublicKey(seed64)

// Build unsigned transaction
const unsignedTx = builder.build('Coin', 'transfer', args, signerPubKey)

// Must provide private key for signing
const { txHash, txPacked } = builder.sign(unsignedTx, privateKey)
```

#### Inspecting Transactions

```typescript
const unsignedTx = builder.buildTransfer({
	recipient: address,
	amount: 10.5,
	symbol: 'AMA'
})

// Inspect transaction details
console.log('Signer:', toBase58(unsignedTx.tx.signer))
console.log('Nonce:', unsignedTx.tx.nonce.toString())
console.log('Contract:', unsignedTx.tx.action.contract)
console.log('Function:', unsignedTx.tx.action.function)
console.log('Args:', unsignedTx.tx.action.args)
console.log('Hash:', toBase58(unsignedTx.hash))
```

### Next Steps

* [**API Modules**: Learn about all available API endpoints](/sdk/5.-api-modules.md)
* [**Examples**: See complete transaction examples](/sdk/7.-examples.md)
* [**Best Practices**: Security and development guidelines](/sdk/8.-best-practices.md)
