> ## Documentation Index
> Fetch the complete documentation index at: https://docs.layerzero.network/llms.txt
> Use this file to discover all available pages before exploring further.

# IOTA L1 OFT SDK

> The LayerZero IOTA L1 OFT SDK provides TypeScript utilities for interacting with OFT contracts on the IOTA L1 blockchain, enabling seamless crosschain...

The LayerZero IOTA L1 OFT SDK provides TypeScript utilities for interacting with OFT contracts on the IOTA L1 blockchain, enabling seamless crosschain token transfers.

## Installation

Install both the core IOTA SDK and the OFT-specific SDK:

```bash wrap theme={null}
npm install @layerzerolabs/lz-iotal1-sdk-v2 @layerzerolabs/lz-iotal1-oft-sdk-v2
```

Or with yarn:

```bash wrap theme={null}
yarn add @layerzerolabs/lz-iotal1-sdk-v2 @layerzerolabs/lz-iotal1-oft-sdk-v2
```

## Setup

### Initialize the SDKs

The recommended pattern uses automatic address fetching from the protocol SDK:

```typescript wrap theme={null}
import {IOTAClient} from '@iota/iota-sdk/client';
import {SDK} from '@layerzerolabs/lz-iotal1-sdk-v2';
import {OFT} from '@layerzerolabs/lz-iotal1-oft-sdk-v2';
import {Stage} from '@layerzerolabs/lz-definitions';

// Setup IOTA client
const client = new IOTAClient({url: 'https://fullnode.mainnet.iota.io:443'});

// Initialize protocol SDK (automatically fetches LayerZero protocol addresses)
const sdk = new SDK({client, stage: Stage.MAINNET});

// Initialize OFT SDK with your OFT package ID
const oft = new OFT(
  sdk, // Protocol SDK instance
  oftPackageId, // Your OFT package ID (NOT OFT CallCap ID!)
  oftObjectId, // Optional: OFT object ID (set after init)
  tokenType, // Optional: "0x123::mycoin::MYCOIN"
  oappObjectId, // Optional: OApp object ID
  adminCapId, // Optional: Admin cap ID (can query later)
);
```

**Critical Notes**:

* **First parameter**: Use your OFT **package ID** (where your code is deployed)
* **SDK automatic addresses**: No need to hardcode LayerZero protocol addresses
* **Optional parameters**: Can be `undefined` initially and set later
* **After initialization**: Update `oft.oftObjectId = newObjectId`

**Getting OApp Instance** (for peer/DVN configuration):

```typescript wrap theme={null}
// Use SDK to get OApp instance (recommended)
const oapp = sdk.getOApp(oftPackageId); // Use package ID

// Configure peers and DVNs through OApp
await oapp.setPeerMoveCall(tx, dstEid, peerBytes);
await oapp.setConfigMoveCall(tx, lib, eid, configType, config);
```

**Do NOT manually instantiate** `new OApp(...)` - this will fail to find your OApp in the registry. Always use `sdk.getOApp(packageId)`.

## SDK Architecture

The IOTA OFT SDK consists of two complementary SDKs:

### Base SDK (`@layerzerolabs/lz-iotal1-sdk-v2`)

Provides core LayerZero protocol functionality:

* **OApp operations**: Peer configuration, messaging, registration
* **Endpoint interaction**: Channel initialization, library configuration
* **DVN/Executor configuration**: Security stack setup
* **Protocol address exports**: All deployed contract addresses

**When to use**: For OApp configuration, peer setup, DVN configuration, and general protocol interactions.

### OFT SDK (`@layerzerolabs/lz-iotal1-oft-sdk-v2`)

Extends the base SDK with OFT-specific functionality:

* **OFT initialization**: `initOftMoveCall()`, `initOftAdapterMoveCall()`
* **Registration**: `registerOAppMoveCall()` (auto-generates lz\_receive\_info)
* **Rate limiting**: Per-pathway token flow limits
* **Fee management**: Crosschain fee configuration
* **Pause control**: Emergency pause functionality

**When to use**: For OFT deployment, token-specific operations, and OFT lifecycle management.

### Relationship

```typescript wrap theme={null}
// Base SDK provides OApp functionality
const sdk = new SDK({ client, stage: Stage.MAINNET });
const oapp = sdk.getOApp(packageId); // OApp configuration

// OFT SDK extends with token-specific features
const oft = new OFT(sdk, oftPackageId, ...); // OFT operations
```

## SDK Address Exports

The SDK provides address exports for all protocol contracts, eliminating hardcoded values:

```typescript wrap theme={null}
import {
  // Object addresses (shared instances everyone uses)
  OBJECT_ENDPOINT_V2_ADDRESS,
  OBJECT_ULN_302_ADDRESS,

  // Package addresses (where code lives)
  PACKAGE_OAPP_ADDRESS,
  PACKAGE_ULN_302_ADDRESS,
  PACKAGE_DVN_LAYERZERO_ADDRESS,

  // Helpers
  OAppUlnConfigBcs,
  Stage,
} from '@layerzerolabs/lz-iotal1-sdk-v2';

// Get addresses for your network
const endpointObj = OBJECT_ENDPOINT_V2_ADDRESS[Stage.MAINNET];
const uln302Obj = OBJECT_ULN_302_ADDRESS[Stage.MAINNET];
const uln302Pkg = PACKAGE_ULN_302_ADDRESS[Stage.MAINNET];
const dvnLayerZero = PACKAGE_DVN_LAYERZERO_ADDRESS[Stage.MAINNET];
```

**Benefits**:

* **Network switching**: Toggle between mainnet/testnet via `Stage` enum
* **SDK updates**: Address changes handled automatically
* **No magic numbers**: Self-documenting configuration
* **Type safety**: TypeScript ensures correct usage

**Available exports**:

| Export                          | Description            | Usage                      |
| ------------------------------- | ---------------------- | -------------------------- |
| `OBJECT_ENDPOINT_V2_ADDRESS`    | Endpoint shared object | Pass to Endpoint functions |
| `OBJECT_ULN_302_ADDRESS`        | ULN302 shared object   | Pass to ULN302 functions   |
| `PACKAGE_OAPP_ADDRESS`          | OApp package ID        | Reference for OApp code    |
| `PACKAGE_ULN_302_ADDRESS`       | ULN302 package ID      | Use in move call targets   |
| `PACKAGE_DVN_LAYERZERO_ADDRESS` | LayerZero DVN package  | DVN configuration          |
| `OAppUlnConfigBcs`              | Config serializer      | Encode DVN configuration   |

## Complete Working Example

<Tip>
  ### Reference Implementation

  All examples on this page are based on proven mainnet deployments. The complete reference implementation demonstrating these patterns is available in the LayerZero test repository at `test-repo/iota-oft-complete/deploy_with_oft_sdk.mjs`.
</Tip>

Based on proven mainnet deployment:

```typescript wrap theme={null}
import {IOTAClient} from '@iota/iota-sdk/client';
import {Transaction} from '@iota/iota-sdk/transactions';
import {Ed25519Keypair} from '@iota/iota-sdk/keypairs/ed25519';
import {SDK} from '@layerzerolabs/lz-iotal1-sdk-v2';
import {OFT} from '@layerzerolabs/lz-iotal1-oft-sdk-v2';
import {Stage} from '@layerzerolabs/lz-definitions';

const client = new IOTAClient({url: 'https://fullnode.mainnet.iota.io:443'});
const keypair = Ed25519Keypair.fromSecretKey(secretKeyBytes);

// Initialize protocol SDK
const sdk = new SDK({client, stage: Stage.MAINNET});

// Initialize OFT SDK
const oft = new OFT(sdk, oftPackageId, undefined, tokenType, oappObjectId);

// Step 1: Initialize OFT
const initTx = new Transaction();
const [adminCap, migrationCap] = oft.initOftMoveCall(
  initTx,
  tokenType,
  ticketObjectId,
  oappObjectId,
  treasuryCapId,
  coinMetadataId,
  6, // shared_decimals
);
initTx.transferObjects([adminCap, migrationCap], sender);

const initResult = await client.signAndExecuteTransaction({
  transaction: initTx,
  signer: keypair,
  options: {showObjectChanges: true},
});

// ✅ CRITICAL: Wait for finality before referencing created objects
await client.waitForTransaction({digest: initResult.digest});

const oftObjectId = initResult.objectChanges.find(
  (c) => c.type === 'created' && c.objectType.includes('oft::OFT<'),
).objectId;

// Update OFT SDK with object ID
oft.oftObjectId = oftObjectId;

// Step 2: Register (SDK auto-generates lz_receive_info)
const regTx = new Transaction();
await oft.registerOAppMoveCall(
  regTx,
  tokenType,
  oftObjectId,
  oappObjectId,
  '0xfe5be5a2d5b11e635e3e4557bb125fb24a3dd09111eded06fd6058b2aee1d054', // OFTComposerManager (IOTA mainnet)
);

const regResult = await client.signAndExecuteTransaction({transaction: regTx, signer: keypair});

// Wait for finality before next operation
await client.waitForTransaction({digest: regResult.digest});

// Step 3: Configure via OApp SDK
const oapp = sdk.getOApp(oftPackageId);

const peerTx = new Transaction();
await oapp.setPeerMoveCall(peerTx, dstEid, peerBytes);
await client.signAndExecuteTransaction({transaction: peerTx, signer: keypair});

console.log('Deployment complete!');
```

**Key SDK Methods Used**:

* `oft.initOftMoveCall()` - Initialize OFT with treasury
* `oft.registerOAppMoveCall()` - Register and auto-generate lz\_receive\_info
* `sdk.getOApp()` - Get OApp instance for configuration
* `oapp.setPeerMoveCall()` - Configure peer addresses
* `oapp.setConfigMoveCall()` - Configure DVNs/executors

***

## Available SDK Methods

### Base SDK (OApp Operations)

The base SDK provides methods for OApp configuration through `sdk.getOApp(packageId)`:

```typescript wrap theme={null}
const oapp = sdk.getOApp(packageId);

// Peer Configuration
await oapp.setPeerMoveCall(tx, eid, peerBytes); // Set peer for destination
await oapp.hasPeer(eid); // Check if peer configured
await oapp.getPeer(eid); // Get peer address

// DVN/Executor Configuration
await oapp.setConfigMoveCall(tx, lib, eid, configType, config); // Set DVN/executor config
await oapp.getConfig(lib, eid, configType); // Get current config

// OApp Registration
await oapp.registerOAppMoveCall(tx, oappObjectId, oappInfo); // Register with Endpoint
await oapp.setOAppInfoMoveCall(tx, oappInfo); // Update OApp info

// Enforced Options
await oapp.setEnforcedOptionsMoveCall(tx, eid, msgType, options); // Set minimum execution params
await oapp.getEnforcedOptions(eid, msgType); // Get enforced options
await oapp.combineOptions(eid, msgType, extraOptions); // Combine with user options

// Admin Operations
await oapp.setDelegateMoveCall(tx, newDelegate); // Transfer admin rights
await oapp.setSendLibraryMoveCall(tx, dstEid, library); // Set custom send library
await oapp.setReceiveLibraryMoveCall(tx, srcEid, library, grace); // Set custom receive library

// Channel Management
await oapp.initChannelMoveCall(tx, remoteEid, remoteOApp); // Initialize messaging channel
await oapp.skipMoveCall(tx, srcEid, sender, nonce); // Skip stuck message
await oapp.clearMoveCall(tx, srcEid, sender, nonce, guid, msg); // Clear verified message
```

### OFT SDK Methods

The OFT SDK provides token-specific operations:

```typescript wrap theme={null}
const oft = new OFT(sdk, oftPackageId, oftObjectId, tokenType, oappObjectId);

// Initialization
initOftMoveCall(tx, coinType, ticket, oapp, treasury, metadata, sharedDecimals);
initOftAdapterMoveCall(tx, coinType, ticket, oapp, metadata, sharedDecimals);

// Registration (auto-generates lz_receive_info!)
await registerOAppMoveCall(tx, coinType, oftObj, oappObj, composerMgr);

// Rate Limiting
await setRateLimitMoveCall(tx, eid, inbound, limit, windowSeconds); // Set rate limit
await unsetRateLimitMoveCall(tx, eid, inbound); // Remove rate limit
await rateLimitConfig(eid, inbound); // Get config
await rateLimitCapacity(eid, inbound); // Get remaining capacity
await rateLimitInFlight(eid, inbound); // Get current usage

// Fee Management
await setFeeBpsMoveCall(tx, eid, feeBps); // Set fee for pathway
await setDefaultFeeBpsMoveCall(tx, feeBps); // Set default fee
await setFeeDepositAddressMoveCall(tx, address); // Set fee recipient
await unsetFeeBpsMoveCall(tx, eid); // Remove pathway fee
await effectiveFeeBps(eid); // Get effective fee
await defaultFeeBps(); // Get default fee
await feeDepositAddress(); // Get fee recipient
await hasOftFee(eid); // Check if fee configured

// Pause Control
await setPauseMoveCall(tx, paused); // Pause/unpause OFT
await isPaused(); // Check pause status

// Queries
await sharedDecimals(); // Get shared decimals
await decimalConversionRate(); // Get conversion rate
await isAdapter(); // Check if adapter mode
await adminCap(); // Get AdminCap address
await oappObject(); // Get OApp object ID
await oftVersion(); // Get OFT version
await coinMetadata(); // Get metadata ID
```

***

## Core Methods

### quote()

Get a fee quote for sending tokens crosschain:

```typescript wrap theme={null}
const {nativeFee, lzTokenFee} = await oft.quote(
  client,
  {
    payer: keypair.toIOTAAddress(),
    tokenMint: '0x...', // Coin type
    tokenEscrow: '0x...', // OFT escrow object
  },
  {
    dstEid: 30101, // Destination endpoint ID (e.g., Ethereum)
    to: Buffer.from('0x' + '1'.repeat(64), 'hex'), // 32-byte recipient address
    amountLD: BigInt(1000000), // Amount in local decimals
    minAmountLD: BigInt(950000), // Minimum amount (slippage)
    options: Buffer.from([]), // Execution options
    composeMsg: undefined, // Optional compose message
    payInLzToken: false, // Pay fee in native or LZ token
  },
);

console.log(`Native fee: ${nativeFee} wei`);
console.log(`LZ token fee: ${lzTokenFee} wei`);
```

### send()

Send tokens crosschain:

```typescript wrap theme={null}
const receipt = await oft.send(
  client,
  {
    payer: keypair, // Signer keypair
    tokenMint: '0x...', // Coin type
    tokenEscrow: '0x...', // OFT escrow object
    tokenSource: '0x...', // Source token account
  },
  {
    dstEid: 30101,
    to: Buffer.from(recipientBytes32),
    amountLD: BigInt(1000000),
    minAmountLD: BigInt(950000),
    options: Buffer.from([]),
    composeMsg: undefined,
    nativeFee: nativeFee,
    lzTokenFee: BigInt(0),
  },
);

console.log('Transaction:', receipt.digest);
```

### getOFTConfig()

Read OFT configuration:

```typescript wrap theme={null}
const config = await oft.getOFTConfig(client);

console.log('Token type:', config.tokenType);
console.log('Shared decimals:', config.sharedDecimals);
console.log('Endpoint:', config.endpoint);
```

### getPeer()

Get peer OFT address for a specific chain:

```typescript wrap theme={null}
const peer = await oft.getPeer(client, 30101); // Ethereum

console.log('Peer address:', Buffer.from(peer).toString('hex'));
```

## Building Execution Options

Use the `Options` helper from the core SDK:

```typescript wrap theme={null}
import {Options} from '@layerzerolabs/lz-v2-utilities';

// For EVM destination
const options = Options.newOptions()
  .addExecutorLzReceiveOption(60000, 0) // gas limit, msg.value
  .toBytes();

// For IOTA/Solana destination with ATA/object creation
const optionsWithValue = Options.newOptions()
  .addExecutorLzReceiveOption(200000, 2039280) // gas + rent
  .toBytes();
```

### Gas Limits by Destination

| Destination | Recommended Gas Limit | Notes                                  |
| ----------- | --------------------- | -------------------------------------- |
| EVM chains  | 60,000 - 200,000      | Higher for complex logic               |
| Solana      | 200,000               | May need msg.value for ATA             |
| IOTA        | 200,000+              | May need msg.value for object creation |
| Aptos       | 100,000               | Adjust based on complexity             |

### msg.value Considerations

When sending **to IOTA**, you may need to include `msg.value` for:

* Creating a new coin object for the recipient
* Storage rent for the new object

Calculate rent based on object size (typically \~0.002 IOTA).

## Complete Example

### Sending Tokens from IOTA to Ethereum

```typescript wrap theme={null}
import {IOTAClient, getFullnodeUrl} from '@iota/iota-sdk/client';
import {Ed25519Keypair} from '@iota/iota-sdk/keypairs/ed25519';
import {OFT} from '@layerzerolabs/lz-iotal1-oft-sdk-v2';
import {Options} from '@layerzerolabs/lz-v2-utilities';

async function sendTokens() {
  // Setup
  const client = new IOTAClient({url: getFullnodeUrl('mainnet')});
  const keypair = Ed25519Keypair.deriveKeypair(process.env.MNEMONIC!);

  const oft = new OFT({
    client,
    oftAddress: process.env.OFT_PACKAGE!,
    oftStoreId: process.env.OFT_STORE!,
  });

  // Prepare params
  const dstEid = 30101; // Ethereum
  const recipient = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'; // Remove 0x, pad to 32 bytes
  const recipientBytes32 = Buffer.from(recipient.padStart(64, '0'), 'hex');
  const amount = BigInt(1_000000); // 1 token (6 decimals)
  const minAmount = BigInt(950000); // 5% slippage

  // Build options
  const options = Options.newOptions().addExecutorLzReceiveOption(60000, 0).toBytes();

  // Quote fee
  console.log('Getting quote...');
  const {nativeFee} = await oft.quote(
    client,
    {
      payer: keypair.toIOTAAddress(),
      tokenMint: process.env.TOKEN_TYPE!,
      tokenEscrow: process.env.OFT_ESCROW!,
    },
    {
      dstEid,
      to: recipientBytes32,
      amountLD: amount,
      minAmountLD: minAmount,
      options: Buffer.from(options),
      composeMsg: undefined,
      payInLzToken: false,
    },
  );

  console.log(`Fee: ${nativeFee / BigInt(1e9)} IOTA`);

  // Send tokens
  console.log('Sending tokens...');
  const receipt = await oft.send(
    client,
    {
      payer: keypair,
      tokenMint: process.env.TOKEN_TYPE!,
      tokenEscrow: process.env.OFT_ESCROW!,
      tokenSource: process.env.TOKEN_ACCOUNT!,
    },
    {
      dstEid,
      to: recipientBytes32,
      amountLD: amount,
      minAmountLD: minAmount,
      options: Buffer.from(options),
      composeMsg: undefined,
      nativeFee,
      lzTokenFee: BigInt(0),
    },
  );

  console.log('- Sent!');
  console.log('Transaction:', receipt.digest);
  console.log('Track at: https://layerzeroscan.com');
}

sendTokens().catch(console.error);
```

## Reading Token Balances

Check OFT token balances using the IOTA client:

```typescript wrap theme={null}
import {IOTAClient} from '@iota/iota-sdk/client';

const client = new IOTAClient({url: getFullnodeUrl('mainnet')});

// Get all coins of a specific type for an address
const coins = await client.getCoins({
  owner: '0x...',
  coinType: '0x...::token::TOKEN',
});

const totalBalance = coins.data.reduce((sum, coin) => sum + BigInt(coin.balance), BigInt(0));

console.log('Balance:', totalBalance.toString());
```

## Integration with Core SDK

The OFT SDK builds on the core IOTA SDK:

```typescript wrap theme={null}
import {createEndpointClient, createOAppClient} from '@layerzerolabs/lz-iotal1-sdk-v2';

// For lower-level Endpoint interactions
const endpoint = createEndpointClient({
  address: '0x...',
});

// For OApp functionality
const oapp = createOAppClient({
  address: '0x...',
});
```

## Error Handling

Common errors and how to handle them:

```typescript wrap theme={null}
try {
  const receipt = await oft.send(/* ... */);
} catch (error) {
  if (error.message.includes('Insufficient funds')) {
    console.error('Not enough tokens or IOTA for gas');
  } else if (error.message.includes('Invalid peer')) {
    console.error('Peer not configured for destination chain');
  } else if (error.message.includes('Channel not initialized')) {
    console.error('Must initialize channel first');
  } else {
    console.error('Unknown error:', error);
  }
}
```

## Admin Functions

The SDK provides admin functions for OFT management (requires `AdminCap`):

### Pause/Unpause

```typescript wrap theme={null}
// Pause OFT operations (emergency)
await oft.setPauseMoveCall(tx, true);

// Unpause
await oft.setPauseMoveCall(tx, false);
```

### Fee Configuration

```typescript wrap theme={null}
// Set default fee rate (in basis points, 10000 = 100%)
await oft.setDefaultFeeBpsMoveCall(tx, 30); // 0.3% fee

// Set fee for specific destination
await oft.setFeeBpsMoveCall(tx, 30101, 50); // 0.5% for Ethereum

// Set fee deposit address
await oft.setFeeDepositAddressMoveCall(tx, feeRecipientAddress);
```

### Rate Limiting

```typescript wrap theme={null}
// Set outbound rate limit
await oft.setOutboundRateLimitMoveCall(tx, {
  dstEid: 30101,
  limit: BigInt(1000000), // Max tokens per window
  window: 86400, // 24 hours in seconds
});

// Set inbound rate limit
await oft.setInboundRateLimitMoveCall(tx, {
  srcEid: 30101,
  limit: BigInt(1000000),
  window: 86400,
});
```

### Peer Configuration

```typescript wrap theme={null}
// Set peer OFT on destination chain
await oft.setPeerMoveCall(tx, 30101, peerBytes32);
```

## Best Practices

1. **Always Quote First**: Get fee estimates before sending
2. **Set Slippage**: Use `minAmountLD` to protect against dust/precision loss
3. **Check Balances**: Verify sufficient tokens and IOTA for gas
4. **Use TypeScript**: Leverage type safety for parameter validation
5. **Test on Testnet**: Always test on testnet before mainnet deployments
6. **Monitor Rate Limits**: Configure appropriate limits for production
7. **Secure Admin Cap**: Use multisig or hardware wallet for admin operations

## Next Steps

* [OFT Overview](/v2/developers/iota/oft/overview) - OFT architecture and deployment guide
* [Configuration Guide](/v2/developers/iota/configuration/dvn-executor-config) - DVN, executor, and gas setup
* [OApp Overview](/v2/developers/iota/oapp/overview) - Base messaging standard
* [Technical Overview](/v2/developers/iota/technical-overview) - IOTA fundamentals and architecture
* [Protocol Overview](/v2/developers/iota/protocol-overview) - Complete message workflows
* [Troubleshooting](/v2/developers/iota/troubleshooting/common-errors) - Common SDK issues
