# 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ama.one/sdk/4.-transaction-building.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
