Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.flashnet.xyz/llms.txt

Use this file to discover all available pages before exploring further.

Call transferPosition on the FlashnetClient with a pool ID, the recipient’s public key, and shape-specific options. The sender must already hold an active lock on the position.

Signature

await client.transferPosition(
  poolId: string,
  newOwnerPublicKey: string,
  opts?: {
    tickLower?: number;             // V3 only
    tickUpper?: number;             // V3 only
    lpTokensToTransfer?: string;    // V2 only; positive integer string
  }
): Promise<TransferPositionResponse>;
Pick one shape per call. Supplying both V2 (lpTokensToTransfer) and V3 (tickLower/tickUpper) options together is rejected before the message is signed.

Transfer a V2 position

// Transfer 5_000 LP shares to a new owner
const response = await client.transferPosition(
  poolId,
  recipientPublicKey,
  { lpTokensToTransfer: "5000" }
);

console.log("Accepted:", response.accepted);
console.log("Confirmation:", response.confirmation);
lpTokensToTransfer is a decimal string of atomic LP units. The validator rejects:
  • Non-digit characters (no sign, no decimal point, no scientific notation, no whitespace).
  • Leading zeros ("01", "007").
  • Zero or negative values.
The sender’s residual after the call is lp_shares[sender] - lpTokensToTransfer. If the residual is zero, the server deletes the sender’s lock row in the same transaction. Otherwise the sender’s lock stays at its original expiry.

Transfer a V3 position

// Transfer a concentrated position to a new owner
const response = await client.transferPosition(
  poolId,
  recipientPublicKey,
  { tickLower: -69080, tickUpper: -66850 }
);
Both tickLower and tickUpper are required. The pair must match an existing position the caller owns. The transfer moves the whole (pool_id, sender, tickLower, tickUpper) row to the recipient and rewrites the matching lp_locks row.

Locking the recipient on V2

A V2 transfer carries the sender’s lock to the recipient only when the recipient has no prior lp_shares in the pool. To deliver locked shares to a recipient who already holds an unlocked position, the recipient must lock first:
// Recipient locks their position at the desired expiry
await recipientClient.lockPosition(poolId, "1793836800");

// Sender then transfers
await senderClient.transferPosition(
  poolId,
  recipientPublicKey,
  { lpTokensToTransfer: "5000" }
);
After the transfer, the recipient’s combined balance (existing + transferred) sits under the recipient’s existing lock terms. The sender’s lock metadata is not propagated.

Verify the result

The transfer commits two pieces of state. Read both to confirm:
import type { LpLockInfo } from "@flashnet/sdk";

// V2: confirm shares moved
const senderPosition = await client.getLpPosition(poolId, senderPublicKey);
const recipientPosition = await client.getLpPosition(poolId, recipientPublicKey);

console.log("Sender residual:", senderPosition.lpTokensOwned);
console.log("Recipient balance:", recipientPosition.lpTokensOwned);

// Confirm lock-follow
const locks: LpLockInfo[] = await client.getPositionLocks(poolId);
const recipientLock = locks.find(l => l.ownerPublicKey === recipientPublicKey);
console.log("Recipient lock expiry:", recipientLock?.lockUntilTimestamp);
For a V3 transfer, query getLpPosition with the recipient’s key and the original tick range; the row should now exist under the recipient. The sender’s row at the same range no longer exists.

Error handling

import { isFlashnetError } from "@flashnet/sdk";

try {
  await client.transferPosition(
    poolId,
    recipientPublicKey,
    { lpTokensToTransfer: "5000" }
  );
} catch (error) {
  if (isFlashnetError(error)) {
    console.error(error.errorCode, error.userMessage);
  }
  throw error;
}
Common rejection paths:
  • No active lock. The sender holds no lp_locks row for the source position, or the lock has expired. Set or extend a lock first.
  • Insufficient lp_shares (V2). The requested amount is greater than lp_shares[sender]. Read the sender’s balance and adjust.
  • Position not found (V3). The (tickLower, tickUpper) pair does not match a position the sender owns.
  • Self-transfer. newOwnerPublicKey equals the caller’s key. Rejected client-side before signing.
  • Mixed shape. Both lpTokensToTransfer and a tick pair were supplied. Pick one.

Concurrency

Two parallel transfers from the same sender on the same pool serialize through the per-pool batch executor. The first commit wins; the second sees the new state and is rejected with insufficient lp_shares (V2) or position does not exist (V3). No race lets the sender double-spend the same balance.

Limitations

  • V3 transfer moves the entire position at one tick range. Partial V3 transfer requires decreaseLiquidity followed by an off-chain delivery.
  • The intent is signed once and cannot be revoked. A typo in newOwnerPublicKey is recoverable only if the recipient cooperates.
  • There is no on-chain record of a transfer. State lives in the Flashnet settlement database.

Next steps