# launch:migration-positions — Ammora SDK 0.3.8 Resolve all Graduation NFTs, including rounding dust, from one canonical receipt. npm run launch:migration-positions -- [Read in Docs](https://docs.ammora.org/examples?command=launch%3Amigration-positions) · [Detailed guide](https://docs.ammora.org/guides/launchpad) · [Local setup](https://docs.ammora.org/guides/example-setup) ## Context and execution GIWA Sepolia 91342. Public SDK 0.3.8, Math 0.3.0. Source revision: `a9b3fd798fa51495e8defec99a25de59e4d70672`. Install the SDK in your app, or prepare the example project using the local setup guide for its npm commands. Review the entry source and guide for required arguments, roles and deployment selection. `AMMORA_SEND=0` does not turn send-only commands into previews. Never put CLI keys in a browser or resend an uncertain transaction. Command: `npm run launch:migration-positions -- ` (`tsx src/launch/migration-positions.ts`). Omit `--` and arguments when the source accepts none. ## Entry file: `src/launch/migration-positions.ts` ```typescript /** Resolve all Graduation NFTs, including rounding dust, from one canonical receipt. * npm run launch:migration-positions -- */ import { AMMORA_FEE_POLICY_VERSION, AmmoraV4LaunchCurveClient, ammoraLaunchFactoryV4Abi, ammConcentratedPositionManagerV3Abi, giwaSepoliaAutoRoute, resolveAmmoraFinalizedHead, parseAmmoraV4MigratedReceipt, resolveAmmoraV4ConcentratedMigrationPositionsReceipt, verifyAmmoraV4CoreAtBlock, } from "@ammora-protocol/sdk"; import { zeroAddress, type Hex } from "viem"; import { address, reader, v4Deployment } from "../shared/env.js"; import { printJson } from "../shared/input.js"; import { main } from "../shared/run.js"; main(async () => { const [curveArg, hashArg, ...extra] = process.argv.slice(2); const curve = address("AMMORA_LAUNCH_CURVE", curveArg); if (!hashArg || !/^0x[0-9a-fA-F]{64}$/.test(hashArg) || extra.length) throw new Error("usage: "); const hash = hashArg as Hex; const client = await reader(); if (client.chain?.id !== giwaSepoliaAutoRoute.chainId) throw new Error("No reviewed confirmation policy for this chain"); const receipt = await client.getTransactionReceipt({ hash }); const confirmedHead = await resolveAmmoraFinalizedHead([client], giwaSepoliaAutoRoute.finality); if (receipt.status !== "success" || confirmedHead < receipt.blockNumber) throw new Error("Migration is reverted or outside the confirmed snapshot; reconcile and wait before continuing"); const block = await client.getBlock({ blockNumber: receipt.blockNumber }); if (block.hash !== receipt.blockHash) throw new Error("Migration receipt is not canonical"); const launch = v4Deployment().contracts.launch; const core = { configRegistry: launch.configRegistry, launchFactory: launch.factory, graduationAdapter: launch.graduationAdapter, feePolicy: launch.feePolicy }; await verifyAmmoraV4CoreAtBlock(client, core, receipt.blockNumber); const state = await new AmmoraV4LaunchCurveClient(client, curve, core).getStateAtBlock(receipt.blockNumber); const token = await client.readContract({ address: launch.factory, abi: ammoraLaunchFactoryV4Abi, functionName: "tokenForCurve", args: [curve], blockNumber: receipt.blockNumber }); if (token === zeroAddress || token.toLowerCase() !== state.baseToken.toLowerCase() || state.feePolicy.toLowerCase() !== launch.feePolicy.toLowerCase() || state.feePolicyVersion !== AMMORA_FEE_POLICY_VERSION || state.status !== 3) throw new Error("Curve is not graduated under the selected factory and fee policy"); const migrated = parseAmmoraV4MigratedReceipt(receipt, curve); if (migrated.canonicalPool.toLowerCase() !== state.canonicalPool.toLowerCase()) throw new Error("Migration event and curve disagree on canonical pool"); const resolved = resolveAmmoraV4ConcentratedMigrationPositionsReceipt({ chainId: client.chain.id, receipt, expectedTransactionHash: hash, curve, positionManager: launch.concentratedPositionManager, canonicalPool: migrated.canonicalPool, creator: state.creator, partner: state.partnerRecipient }); const positions = []; for (const position of resolved.positions) { const storage = await client.readContract({ address: launch.concentratedPositionManager, abi: ammConcentratedPositionManagerV3Abi, functionName: "getPosition", args: [position.tokenId], blockNumber: receipt.blockNumber }); const owner = await client.readContract({ address: launch.concentratedPositionManager, abi: ammConcentratedPositionManagerV3Abi, functionName: "ownerOf", args: [position.tokenId], blockNumber: receipt.blockNumber }); if (owner.toLowerCase() !== position.owner.toLowerCase() || storage.pool.toLowerCase() !== migrated.canonicalPool.toLowerCase()) throw new Error("Position receipt and storage identity differ"); positions.push({ ...position, storage }); } printJson({ ...resolved, positions, blockNumber: receipt.blockNumber, blockHash: receipt.blockHash, confirmedHead, note: "Receipt-time ownership, not current claim permission. Include every NFT: integer rounding can mint a separate dust position. No permanent-lock event alone is not proof of unlocked state; inspect storage. Shared creator/partner ownership prevents distinct role attribution per NFT. Revalidate ownership and pool bindings before claiming.", }); }); ``` ## Supporting files Keep relative paths intact. Local imports are listed recursively; JSON templates are choices to review, not approved inputs. Runtime-selected files and wallet/environment setup still follow the detailed guide. - [src/shared/env.ts](https://docs.ammora.org/examples/source/src/shared/env.ts.txt) - [src/shared/input.ts](https://docs.ammora.org/examples/source/src/shared/input.ts.txt) - [src/shared/retry.ts](https://docs.ammora.org/examples/source/src/shared/retry.ts.txt) - [src/shared/rpc.ts](https://docs.ammora.org/examples/source/src/shared/rpc.ts.txt) - [src/shared/run.ts](https://docs.ammora.org/examples/source/src/shared/run.ts.txt) - [src/launch/config-document.example.json](https://docs.ammora.org/examples/source/src/launch/config-document.example.json.txt) - [src/launch/team/fees.example.json](https://docs.ammora.org/examples/source/src/launch/team/fees.example.json.txt) - [src/launch/team/recipients.example.json](https://docs.ammora.org/examples/source/src/launch/team/recipients.example.json.txt) - [package.json](https://docs.ammora.org/examples/source/package.json.txt) - [package-lock.json](https://docs.ammora.org/examples/source/package-lock.json.txt) - [tsconfig.json](https://docs.ammora.org/examples/source/tsconfig.json.txt) - [.env.giwa.example](https://docs.ammora.org/examples/source/file.env.giwa.example.txt) - [.nvmrc](https://docs.ammora.org/examples/source/file.nvmrc.txt) ## AI implementation checklist Use the actual SDK exports and parameter types in the linked files. Start with reads and unsigned plans where supported. Use a connected wallet for browser signing. Preserve raw integer units, exact approvals, caller authority, deadlines and receipt reconciliation. Report local checks separately from live-chain verification.