diff --git a/packages/smartcontracts/artifacts/contracts/nft/nftParallelClosedMinter.json b/packages/smartcontracts/artifacts/contracts/nft/nftParallelClosedMinter.json
new file mode 100644
index 0000000..f5c8c93
--- /dev/null
+++ b/packages/smartcontracts/artifacts/contracts/nft/nftParallelClosedMinter.json
@@ -0,0 +1,426 @@
+{
+    "version": 9,
+    "compilerVersion": "1.19.4+commit.cfee948",
+    "contract": "NftParallelClosedMinter",
+    "md5": "12afc68fd84cddd24bc866da2955fb57",
+    "structs": [
+        {
+            "name": "SHPreimage",
+            "params": [
+                {
+                    "name": "txVer",
+                    "type": "bytes"
+                },
+                {
+                    "name": "nLockTime",
+                    "type": "bytes"
+                },
+                {
+                    "name": "hashPrevouts",
+                    "type": "bytes"
+                },
+                {
+                    "name": "hashSpentAmounts",
+                    "type": "bytes"
+                },
+                {
+                    "name": "hashSpentScripts",
+                    "type": "bytes"
+                },
+                {
+                    "name": "hashSequences",
+                    "type": "bytes"
+                },
+                {
+                    "name": "hashOutputs",
+                    "type": "bytes"
+                },
+                {
+                    "name": "spendType",
+                    "type": "bytes"
+                },
+                {
+                    "name": "inputIndex",
+                    "type": "bytes"
+                },
+                {
+                    "name": "hashTapLeaf",
+                    "type": "bytes"
+                },
+                {
+                    "name": "keyVer",
+                    "type": "bytes"
+                },
+                {
+                    "name": "codeSeparator",
+                    "type": "bytes"
+                },
+                {
+                    "name": "_e",
+                    "type": "bytes"
+                },
+                {
+                    "name": "eLastByte",
+                    "type": "int"
+                }
+            ],
+            "genericTypes": []
+        },
+        {
+            "name": "PrevoutsCtx",
+            "params": [
+                {
+                    "name": "prevouts",
+                    "type": "bytes[6]"
+                },
+                {
+                    "name": "inputIndexVal",
+                    "type": "int"
+                },
+                {
+                    "name": "outputIndexVal",
+                    "type": "int"
+                },
+                {
+                    "name": "spentTxhash",
+                    "type": "bytes"
+                },
+                {
+                    "name": "outputIndex",
+                    "type": "bytes"
+                }
+            ],
+            "genericTypes": []
+        },
+        {
+            "name": "BacktraceInfo",
+            "params": [
+                {
+                    "name": "preTx",
+                    "type": "XrayedTxIdPreimg1"
+                },
+                {
+                    "name": "preTxInput",
+                    "type": "TxInput"
+                },
+                {
+                    "name": "preTxInputIndex",
+                    "type": "int"
+                },
+                {
+                    "name": "prePreTx",
+                    "type": "XrayedTxIdPreimg2"
+                }
+            ],
+            "genericTypes": []
+        },
+        {
+            "name": "CAT721State",
+            "params": [
+                {
+                    "name": "ownerAddr",
+                    "type": "bytes"
+                },
+                {
+                    "name": "localId",
+                    "type": "int"
+                }
+            ],
+            "genericTypes": []
+        },
+        {
+            "name": "NftParallelClosedMinterState",
+            "params": [
+                {
+                    "name": "nftScript",
+                    "type": "bytes"
+                },
+                {
+                    "name": "nextLocalId",
+                    "type": "int"
+                }
+            ],
+            "genericTypes": []
+        },
+        {
+            "name": "PreTxStatesInfo",
+            "params": [
+                {
+                    "name": "statesHashRoot",
+                    "type": "bytes"
+                },
+                {
+                    "name": "txoStateHashes",
+                    "type": "bytes[5]"
+                }
+            ],
+            "genericTypes": []
+        },
+        {
+            "name": "XrayedTxIdPreimg1",
+            "params": [
+                {
+                    "name": "version",
+                    "type": "bytes"
+                },
+                {
+                    "name": "inputCount",
+                    "type": "bytes"
+                },
+                {
+                    "name": "inputs",
+                    "type": "bytes[6]"
+                },
+                {
+                    "name": "outputCountVal",
+                    "type": "int"
+                },
+                {
+                    "name": "outputCount",
+                    "type": "bytes"
+                },
+                {
+                    "name": "outputSatoshisList",
+                    "type": "bytes[6]"
+                },
+                {
+                    "name": "outputScriptList",
+                    "type": "bytes[6]"
+                },
+                {
+                    "name": "nLocktime",
+                    "type": "bytes"
+                }
+            ],
+            "genericTypes": []
+        },
+        {
+            "name": "XrayedTxIdPreimg2",
+            "params": [
+                {
+                    "name": "prevList",
+                    "type": "bytes[4]"
+                },
+                {
+                    "name": "outputCountVal",
+                    "type": "int"
+                },
+                {
+                    "name": "outputCount",
+                    "type": "bytes"
+                },
+                {
+                    "name": "outputSatoshisList",
+                    "type": "bytes[6]"
+                },
+                {
+                    "name": "outputScriptList",
+                    "type": "bytes[6]"
+                },
+                {
+                    "name": "nLocktime",
+                    "type": "bytes"
+                }
+            ],
+            "genericTypes": []
+        },
+        {
+            "name": "XrayedTxIdPreimg3",
+            "params": [
+                {
+                    "name": "prev",
+                    "type": "bytes"
+                },
+                {
+                    "name": "outputCountVal",
+                    "type": "int"
+                },
+                {
+                    "name": "outputCount",
+                    "type": "bytes"
+                },
+                {
+                    "name": "outputSatoshisList",
+                    "type": "bytes[4]"
+                },
+                {
+                    "name": "outputScriptList",
+                    "type": "bytes[4]"
+                },
+                {
+                    "name": "nLocktime",
+                    "type": "bytes"
+                }
+            ],
+            "genericTypes": []
+        },
+        {
+            "name": "TxInput",
+            "params": [
+                {
+                    "name": "txhash",
+                    "type": "bytes"
+                },
+                {
+                    "name": "outputIndex",
+                    "type": "bytes"
+                },
+                {
+                    "name": "outputIndexVal",
+                    "type": "int"
+                },
+                {
+                    "name": "sequence",
+                    "type": "bytes"
+                }
+            ],
+            "genericTypes": []
+        },
+        {
+            "name": "ChangeInfo",
+            "params": [
+                {
+                    "name": "script",
+                    "type": "bytes"
+                },
+                {
+                    "name": "satoshis",
+                    "type": "bytes"
+                }
+            ],
+            "genericTypes": []
+        }
+    ],
+    "library": [
+        {
+            "name": "SigHashUtils",
+            "params": [],
+            "properties": [],
+            "genericTypes": []
+        },
+        {
+            "name": "Backtrace",
+            "params": [],
+            "properties": [],
+            "genericTypes": []
+        },
+        {
+            "name": "CAT721Proto",
+            "params": [],
+            "properties": [],
+            "genericTypes": []
+        },
+        {
+            "name": "NftParallelClosedMinterProto",
+            "params": [],
+            "properties": [],
+            "genericTypes": []
+        },
+        {
+            "name": "StateUtils",
+            "params": [],
+            "properties": [],
+            "genericTypes": []
+        },
+        {
+            "name": "TxProof",
+            "params": [],
+            "properties": [],
+            "genericTypes": []
+        },
+        {
+            "name": "TxUtil",
+            "params": [],
+            "properties": [],
+            "genericTypes": []
+        }
+    ],
+    "alias": [],
+    "abi": [
+        {
+            "type": "function",
+            "name": "mint",
+            "index": 0,
+            "params": [
+                {
+                    "name": "curTxoStateHashes",
+                    "type": "bytes[5]"
+                },
+                {
+                    "name": "nftMint",
+                    "type": "CAT721State"
+                },
+                {
+                    "name": "issuerPubKeyPrefix",
+                    "type": "bytes"
+                },
+                {
+                    "name": "issuerPubKey",
+                    "type": "PubKey"
+                },
+                {
+                    "name": "issuerSig",
+                    "type": "Sig"
+                },
+                {
+                    "name": "minterSatoshis",
+                    "type": "bytes"
+                },
+                {
+                    "name": "nftSatoshis",
+                    "type": "bytes"
+                },
+                {
+                    "name": "preState",
+                    "type": "NftParallelClosedMinterState"
+                },
+                {
+                    "name": "preTxStatesInfo",
+                    "type": "PreTxStatesInfo"
+                },
+                {
+                    "name": "backtraceInfo",
+                    "type": "BacktraceInfo"
+                },
+                {
+                    "name": "shPreimage",
+                    "type": "SHPreimage"
+                },
+                {
+                    "name": "prevoutsCtx",
+                    "type": "PrevoutsCtx"
+                },
+                {
+                    "name": "spentScripts",
+                    "type": "bytes[6]"
+                },
+                {
+                    "name": "changeInfo",
+                    "type": "ChangeInfo"
+                }
+            ]
+        },
+        {
+            "type": "constructor",
+            "params": [
+                {
+                    "name": "ownerAddress",
+                    "type": "bytes"
+                },
+                {
+                    "name": "genesisOutpoint",
+                    "type": "bytes"
+                },
+                {
+                    "name": "max",
+                    "type": "int"
+                }
+            ]
+        }
+    ],
+    "stateProps": [],
+    "buildType": "debug",
+    "file": "../nftParallelClosedMinter.scrypt",
+    "hex": "0800000000000000002079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817984c807bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179879be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179842f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a0310000000000<ownerAddress><genesisOutpoint><max>5279567a757171557a78557a75547a547a547a547a76547a7572537a6d750126790126790126790126790126790126790126790126790126790126790126790126790126790126790111795e797e5d797e5c797e5b797e5a797e59797e58797e57797e56797e55797e54797e53797ea8011379787ea85279017f9f695279009c6301006752796878557952797e8801167955797e54798b7e6b6d6d6d6d6d6d6d6d6c775679ad011879011879011879011879011879011879011879011879011879011879012e790129795b795b795b795b795b795b790056766b796c756e7e777755766b796c756e7e777754766b796c756e7e777753766b796c756e7e777752766b796c756e7e777751766b796c756e7e7b756b6d6d6d6c77a852798855796e760087630100776876030000007e527987777777695479537978760087630100776876030000007e527987777777695b795b795b795b795b795b79565c797600a26976569f69948c766b796c756b6d6d6d6c547954797e886d6d6d6d6d6d5e795e795e795e795e795e790128795679567956795679567956790056766b796c756e827752797e7e777755766b796c756e827752797e7e777754766b796c756e827752797e7e777753766b796c756e827752797e7e777752766b796c756e827752797e7e777751766b796c756e827752797e7e7b756b6d6d6d6c77a878886d6d6d75015b79015b79015b79015b79015b79015b790163790163796e7ea97777014c79014c79014c79014c79014c79014c7956007600a26976569f69948c766b796c756b6d6d6d6c0119795879066a1863617401787e77527988577957795779577957795d79007657766b796c75a97e7d7756766b796c75a97e7d7755766b796c75a97e7d7754766b796c75a97e7d7753766b796c75a97e7d77a95279876b6d6d6d6c77695279587958795879587958795557798c7600a26976559f69948c766b796c756b6d6d756c886d6d6d6d75011279009d5e0113797600a26976569f6994766b796c750111790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790157790132790131790131790131790131790131790131790131790131790131790131790131790131790131790131790131790131790131790131790131790131790131790131790131790131790131790116790116797e7601167901167901167901167901167901167955766b796c756b6d6d6d6c7e7d7701167901167901167901167901167901167954766b796c756b6d6d6d6c7e7d7701167901167901167901167901167901167953766b796c756b6d6d6d6c7e7d7701167901167901167901167901167901167952766b796c756b6d6d6d6c7e7d7701167901167901167901167901167901167951766b796c756b6d6d6d6c7e7d7701167901167901167901167901167901167900766b796c756b6d6d6d6c7e7d775f797e775f795f79885d795d795d795d795d795d7955766b796c756b6d6d6d6c58795879587958795879587955766b796c756b6d6d6d6c768277000113799f637052797e53797e7e547a7572537a537975686d755d795d795d795d795d795d7954766b796c756b6d6d6d6c58795879587958795879587954766b796c756b6d6d6d6c768277510113799f637052797e53797e7e547a7572537a537975686d755d795d795d795d795d795d7953766b796c756b6d6d6d6c58795879587958795879587953766b796c756b6d6d6d6c768277520113799f637052797e53797e7e547a7572537a537975686d755d795d795d795d795d795d7952766b796c756b6d6d6d6c58795879587958795879587952766b796c756b6d6d6d6c768277530113799f637052797e53797e7e547a7572537a537975686d755d795d795d795d795d795d7951766b796c756b6d6d6d6c58795879587958795879587951766b796c756b6d6d6d6c768277540113799f637052797e53797e7e547a7572537a537975686d755d795d795d795d795d795d7900766b796c756b6d6d6d6c58795879587958795879587900766b796c756b6d6d6d6c768277550113799f637052797e53797e7e547a7572537a537975686d787752797eaa6b6d6d6d6d6d6d6d6d6d6d6d6d6c88011979011979011979011979707e01007e787e6b6d6d6c012f79012f79012f79012f79012f79012f7956011d797600a26976569f69948c766b796c756b6d6d6d6c8801177901197978760087630100776876030000007e527987777777690119790119797e7653798764011579011579011579011579011579011579011579011579011579011579011579011579011579011579011579011579011579011579011579012d79012c79011679011579011579011579011579011579011579011579011579011579011579011579011579011579011579011579011579011579011579011579007601147901147901147901147953766b796c756b6d6d6c7e7d7701147901147901147901147952766b796c756b6d6d6c7e7d7701147901147901147901147951766b796c756b6d6d6c7e7d7701147901147901147901147900766b796c756b6d6d6c7e775f795f79885d795d795d795d795d795d7955766b796c756b6d6d6d6c58795879587958795879587955766b796c756b6d6d6d6c768277000113799f637052797e53797e7e547a7572537a537975686d755d795d795d795d795d795d7954766b796c756b6d6d6d6c58795879587958795879587954766b796c756b6d6d6d6c768277510113799f637052797e53797e7e547a7572537a537975686d755d795d795d795d795d795d7953766b796c756b6d6d6d6c58795879587958795879587953766b796c756b6d6d6d6c768277520113799f637052797e53797e7e547a7572537a537975686d755d795d795d795d795d795d7952766b796c756b6d6d6d6c58795879587958795879587952766b796c756b6d6d6d6c768277530113799f637052797e53797e7e547a7572537a537975686d755d795d795d795d795d795d7951766b796c756b6d6d6d6c58795879587958795879587951766b796c756b6d6d6d6c768277540113799f637052797e53797e7e547a7572537a537975686d755d795d795d795d795d795d7900766b796c756b6d6d6d6c58795879587958795879587900766b796c756b6d6d6d6c768277550113799f637052797e53797e7e547a7572537a537975686d787752797eaa6b6d6d6d6d6d6d6d6d6d6d6c5379885979597959795979597959795658797600a26976569f69948c766b796c756b6d6d6d6c78886d6d6d6d6d6d6d6d6d6d6d686d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d75013f79013f797e7654798763015c79015c79015c79015c79015c7955517600a26976559f69948c766b796c756b6d6d756c0088015c79015c79015c79015c79015c7955527600a26976559f69948c766b796c756b6d6d756c0088015c79015c79015c79015c79015c7955537600a26976559f69948c766b796c756b6d6d756c0088015c79015c79015c79015c79015c7955547600a26976559f69948c766b796c756b6d6d756c008868000000016179016279938b0162790163799352937858799f635379577901687978827d770122a1696e7e53797e7777777e547a7572537a537975547901657953796e7ea97777a97e557a75547a547a547a547a54797552798b537a757b7b527975687658799f635379577901687978827d770122a1696e7e53797e7777777e547a7572537a537975547901657952796e7ea97777a97e557a75547a547a547a547a54797552798b537a757b7b52797568016a790164799d5479016c79016c7978827701149d6e7ea97777a97e557a75547a547a547a547a54797501647901667978827d770122a1696e7e53797e77777753798b547a7572537a5379755579547901737901737901737901737901737956795679557894000052799f637600a97e77685152799f637600a97e77685252799f637600a97e77685352799f637600a97e77685452799f637600a97e776877777ea9557955795579557955795579007657766b796c75a97e7d7756766b796c75a97e7d7755766b796c75a97e7d7754766b796c75a97e7d7753766b796c75a97e7d77a95279876b6d6d6d6c776976066a1863617401787e770800000000000000007882777e787e6b6d6d6d6d6c770111790111797601127987646e78827d770122a1696e7e53797e77777767006877777857797e53797e787ea876012c79885d79016e79016e797ea988016b79016d79ac6b6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6c77",
+    "sourceMapFile": ""
+}
\ No newline at end of file
diff --git a/packages/smartcontracts/artifacts/contracts/nft/nftParallelClosedMinterProto.json b/packages/smartcontracts/artifacts/contracts/nft/nftParallelClosedMinterProto.json
new file mode 100644
index 0000000..ccd5d30
--- /dev/null
+++ b/packages/smartcontracts/artifacts/contracts/nft/nftParallelClosedMinterProto.json
@@ -0,0 +1,42 @@
+{
+    "version": 9,
+    "compilerVersion": "1.19.4+commit.cfee948",
+    "contract": "NftParallelClosedMinterProto",
+    "md5": "d41d8cd98f00b204e9800998ecf8427e",
+    "structs": [
+        {
+            "name": "NftParallelClosedMinterState",
+            "params": [
+                {
+                    "name": "nftScript",
+                    "type": "bytes"
+                },
+                {
+                    "name": "nextLocalId",
+                    "type": "int"
+                }
+            ],
+            "genericTypes": []
+        }
+    ],
+    "library": [
+        {
+            "name": "NftParallelClosedMinterProto",
+            "params": [],
+            "properties": [],
+            "genericTypes": []
+        }
+    ],
+    "alias": [],
+    "abi": [
+        {
+            "type": "constructor",
+            "params": []
+        }
+    ],
+    "stateProps": [],
+    "buildType": "debug",
+    "file": "../nftParallelClosedMinterProto.scrypt",
+    "hex": "",
+    "sourceMapFile": ""
+}
\ No newline at end of file
diff --git a/packages/smartcontracts/src/contracts/nft/nftParallelClosedMinter.ts b/packages/smartcontracts/src/contracts/nft/nftParallelClosedMinter.ts
new file mode 100644
index 0000000..9220f1a
--- /dev/null
+++ b/packages/smartcontracts/src/contracts/nft/nftParallelClosedMinter.ts
@@ -0,0 +1,171 @@
+import {
+    method,
+    SmartContract,
+    assert,
+    prop,
+    ByteString,
+    sha256,
+    PubKey,
+    Sig,
+    hash160,
+    toByteString,
+} from 'scrypt-ts'
+import { TxUtil, ChangeInfo, STATE_OUTPUT_INDEX, int32 } from '../utils/txUtil'
+import {
+    PrevoutsCtx,
+    SHPreimage,
+    SigHashUtils,
+    SpentScriptsCtx,
+} from '../utils/sigHashUtils'
+import { Backtrace, BacktraceInfo } from '../utils/backtrace'
+import {
+    NftParallelClosedMinterState,
+    NftParallelClosedMinterProto,
+} from './nftParallelClosedMinterProto'
+import {
+    PreTxStatesInfo,
+    StateUtils,
+    TxoStateHashes,
+} from '../utils/stateUtils'
+import { CAT721Proto, CAT721State } from './cat721Proto'
+
+export class NftParallelClosedMinter extends SmartContract {
+    @prop()
+    issuerAddress: ByteString
+
+    @prop()
+    genesisOutpoint: ByteString
+
+    @prop()
+    max: int32
+
+    constructor(
+        ownerAddress: ByteString,
+        genesisOutpoint: ByteString,
+        max: int32
+    ) {
+        super(...arguments)
+        this.issuerAddress = ownerAddress
+        this.genesisOutpoint = genesisOutpoint
+        this.max = max
+    }
+
+    @method()
+    public mint(
+        curTxoStateHashes: TxoStateHashes,
+        // contrat logic args
+        nftMint: CAT721State,
+        issuerPubKeyPrefix: ByteString,
+        issuerPubKey: PubKey,
+        issuerSig: Sig,
+        // contract lock satoshis
+        minterSatoshis: ByteString,
+        nftSatoshis: ByteString,
+        // verify preTx data part
+        preState: NftParallelClosedMinterState,
+        preTxStatesInfo: PreTxStatesInfo,
+
+        // backtrace
+        backtraceInfo: BacktraceInfo,
+
+        // common args
+        // current tx info
+        shPreimage: SHPreimage,
+        prevoutsCtx: PrevoutsCtx,
+        spentScripts: SpentScriptsCtx,
+        // change output info
+        changeInfo: ChangeInfo
+    ) {
+        // check preimage
+        assert(
+            this.checkSig(
+                SigHashUtils.checkSHPreimage(shPreimage),
+                SigHashUtils.Gx
+            ),
+            'preimage check error'
+        )
+        // check ctx
+        SigHashUtils.checkPrevoutsCtx(
+            prevoutsCtx,
+            shPreimage.hashPrevouts,
+            shPreimage.inputIndex
+        )
+        SigHashUtils.checkSpentScriptsCtx(
+            spentScripts,
+            shPreimage.hashSpentScripts
+        )
+        // verify state
+        StateUtils.verifyPreStateHash(
+            preTxStatesInfo,
+            NftParallelClosedMinterProto.stateHash(preState),
+            backtraceInfo.preTx.outputScriptList[STATE_OUTPUT_INDEX],
+            prevoutsCtx.outputIndexVal
+        )
+        // minter need at input 0
+        assert(prevoutsCtx.inputIndexVal == 0n)
+        // check preTx script eq this locking script
+        const preScript = spentScripts[Number(prevoutsCtx.inputIndexVal)]
+        // back to genesis
+        Backtrace.verifyUnique(
+            prevoutsCtx.spentTxhash,
+            backtraceInfo,
+            this.genesisOutpoint,
+            preScript
+        )
+        // is genesis
+        const prevOutpoint =
+            backtraceInfo.preTxInput.txhash +
+            backtraceInfo.preTxInput.outputIndex
+        if (prevOutpoint == this.genesisOutpoint) {
+            // genesis only deploy one minter
+            assert(preTxStatesInfo.txoStateHashes[1] == toByteString(''))
+            assert(preTxStatesInfo.txoStateHashes[2] == toByteString(''))
+            assert(preTxStatesInfo.txoStateHashes[3] == toByteString(''))
+            assert(preTxStatesInfo.txoStateHashes[4] == toByteString(''))
+        }
+        let hashString = toByteString('')
+        let minterOutput = toByteString('')
+        let stateNumber = 0n
+        // next 1
+        const nextLocalId1 = preState.nextLocalId + preState.nextLocalId + 1n
+        // next 2
+        const nextLocalId2 = preState.nextLocalId + preState.nextLocalId + 2n
+        if (nextLocalId1 < this.max) {
+            minterOutput += TxUtil.buildOutput(preScript, minterSatoshis)
+            hashString += hash160(
+                NftParallelClosedMinterProto.stateHash({
+                    nftScript: preState.nftScript,
+                    nextLocalId: nextLocalId1,
+                })
+            )
+            stateNumber += 1n
+        }
+        if (nextLocalId2 < this.max) {
+            minterOutput += TxUtil.buildOutput(preScript, minterSatoshis)
+            hashString += hash160(
+                NftParallelClosedMinterProto.stateHash({
+                    nftScript: preState.nftScript,
+                    nextLocalId: nextLocalId2,
+                })
+            )
+            stateNumber += 1n
+        }
+        assert(nftMint.localId == preState.nextLocalId)
+        hashString += hash160(CAT721Proto.stateHash(nftMint))
+        const nftOutput = TxUtil.buildOutput(preState.nftScript, nftSatoshis)
+        stateNumber += 1n
+        const stateOutput = StateUtils.getCurrentStateOutput(
+            hashString,
+            stateNumber,
+            curTxoStateHashes
+        )
+        const changeOutput = TxUtil.getChangeOutput(changeInfo)
+        const hashOutputs = sha256(
+            stateOutput + minterOutput + nftOutput + changeOutput
+        )
+        assert(hashOutputs == shPreimage.hashOutputs, 'hashOutputs mismatch')
+        // check sig
+        assert(this.issuerAddress == hash160(issuerPubKeyPrefix + issuerPubKey))
+        assert(this.checkSig(issuerSig, issuerPubKey))
+    }
+}
diff --git a/packages/smartcontracts/src/contracts/nft/nftParallelClosedMinterProto.ts b/packages/smartcontracts/src/contracts/nft/nftParallelClosedMinterProto.ts
new file mode 100644
index 0000000..9363c68
--- /dev/null
+++ b/packages/smartcontracts/src/contracts/nft/nftParallelClosedMinterProto.ts
@@ -0,0 +1,37 @@
+import {
+    ByteString,
+    hash160,
+    int2ByteString,
+    method,
+    SmartContractLib,
+} from 'scrypt-ts'
+import { int32 } from '../utils/txUtil'
+
+export type NftParallelClosedMinterState = {
+    nftScript: ByteString
+    nextLocalId: int32
+}
+
+export class NftParallelClosedMinterProto extends SmartContractLib {
+    @method()
+    static stateHash(_state: NftParallelClosedMinterState): ByteString {
+        return hash160(_state.nftScript + int2ByteString(_state.nextLocalId))
+    }
+
+    static create(
+        nftScript: ByteString,
+        nextLocalId: int32
+    ): NftParallelClosedMinterState {
+        return {
+            nftScript: nftScript,
+            nextLocalId: nextLocalId,
+        }
+    }
+
+    static toByteString(closeMinterInfo: NftParallelClosedMinterState) {
+        return (
+            closeMinterInfo.nftScript +
+            int2ByteString(closeMinterInfo.nextLocalId)
+        )
+    }
+}
diff --git a/packages/smartcontracts/src/index.ts b/packages/smartcontracts/src/index.ts
index c17e1e8..79821fe 100644
--- a/packages/smartcontracts/src/index.ts
+++ b/packages/smartcontracts/src/index.ts
@@ -13,12 +13,14 @@ import burnGuard from '../artifacts/contracts/token/burnGuard.json'
 import transferGuard from '../artifacts/contracts/token/transferGuard.json'
 
 import { NftClosedMinter } from './contracts/nft/nftClosedMinter'
+import { NftParallelClosedMinter } from './contracts/nft/nftParallelClosedMinter'
 import { NftOpenMinter } from './contracts/nft/nftOpenMinter'
 import { CAT721 } from './contracts/nft/cat721'
 import { NftTransferGuard } from './contracts/nft/nftTransferGuard'
 import { NftBurnGuard } from './contracts/nft/nftBurnGuard'
 
 import nftClosedMinter from '../artifacts/contracts/nft/nftClosedMinter.json'
+import nftParallelClosedMinter from '../artifacts/contracts/nft/nftParallelClosedMinter.json'
 import nftOpenMinter from '../artifacts/contracts/nft/nftOpenMinter.json'
 import cat721 from '../artifacts/contracts/nft/cat721.json'
 import nftTransferGuard from '../artifacts/contracts/nft/nftTransferGuard.json'
@@ -34,6 +36,7 @@ import nftBurnGuard from '../artifacts/contracts/nft/nftBurnGuard.json'
     TransferGuard.loadArtifact(transferGuard)
     // nft minter
     NftClosedMinter.loadArtifact(nftClosedMinter)
+    NftParallelClosedMinter.loadArtifact(nftParallelClosedMinter)
     NftOpenMinter.loadArtifact(nftOpenMinter)
     // nft
     CAT721.loadArtifact(cat721)
@@ -52,8 +55,10 @@ export * from './contracts/token/openMinterV2'
 export * from './contracts/token/openMinterProto'
 export * from './contracts/token/openMinterV2Proto'
 export * from './contracts/nft/nftClosedMinter'
+export * from './contracts/nft/nftParallelClosedMinter'
 export * from './contracts/nft/nftOpenMinter'
 export * from './contracts/nft/nftClosedMinterProto'
+export * from './contracts/nft/nftParallelClosedMinterProto'
 export * from './contracts/nft/nftOpenMinterProto'
 export * from './contracts/nft/nftOpenMinterMerkleTree'
 export * from './contracts/nft/cat721'
diff --git a/packages/smartcontracts/tests/nft/parallelClosedMinter.ts b/packages/smartcontracts/tests/nft/parallelClosedMinter.ts
new file mode 100644
index 0000000..49ea46e
--- /dev/null
+++ b/packages/smartcontracts/tests/nft/parallelClosedMinter.ts
@@ -0,0 +1,113 @@
+import { NftClosedMinter } from '../../src/contracts/nft/nftClosedMinter'
+import {
+    CatTx,
+    ContractCallResult,
+    ContractIns,
+    TaprootSmartContract,
+} from '../../src/lib/catTx'
+import { CAT721Proto, CAT721State } from '../../src/contracts/nft/cat721Proto'
+import {
+    NftParallelClosedMinterProto,
+    NftParallelClosedMinterState,
+} from '../../src/contracts/nft/nftParallelClosedMinterProto'
+
+export async function nftParallelClosedMinterDeploy(
+    seckey,
+    genesisUtxo,
+    nftClosedMinter: NftClosedMinter,
+    nftClosedMinterTaproot: TaprootSmartContract,
+    nftClosedMinterState: NftParallelClosedMinterState
+): Promise<ContractIns<NftParallelClosedMinterState>> {
+    // tx deploy
+    const catTx = CatTx.create()
+    catTx.tx.from([genesisUtxo])
+    const atIndex = catTx.addStateContractOutput(
+        nftClosedMinterTaproot.lockingScript,
+        NftParallelClosedMinterProto.toByteString(nftClosedMinterState)
+    )
+    catTx.sign(seckey)
+    return {
+        catTx: catTx,
+        contract: nftClosedMinter,
+        state: nftClosedMinterState,
+        contractTaproot: nftClosedMinterTaproot,
+        atOutputIndex: atIndex,
+    }
+}
+
+export async function nftParallelClosedMinterCall(
+    contractIns: ContractIns<NftParallelClosedMinterState>,
+    nftTaproot: TaprootSmartContract,
+    nftState: CAT721State,
+    max: bigint,
+    errorNextLocalId: boolean = false
+): Promise<ContractCallResult<NftParallelClosedMinterState | CAT721State>> {
+    const catTx = CatTx.create()
+    const atInputIndex = catTx.fromCatTx(
+        contractIns.catTx,
+        contractIns.atOutputIndex
+    )
+    const nexts: ContractIns<NftParallelClosedMinterState | CAT721State>[] = []
+    //
+    let nextLocalId1 =
+        contractIns.state.nextLocalId + contractIns.state.nextLocalId + 1n
+    const nextLocalId2 =
+        contractIns.state.nextLocalId + contractIns.state.nextLocalId + 2n
+    if (errorNextLocalId) {
+        nextLocalId1 = nextLocalId1 + 1n
+    }
+    if (nextLocalId1 < max) {
+        const nextState = NftParallelClosedMinterProto.create(
+            contractIns.state.nftScript,
+            nextLocalId1
+        )
+        const atOutputIndex = catTx.addStateContractOutput(
+            contractIns.contractTaproot.lockingScript,
+            NftParallelClosedMinterProto.toByteString(nextState)
+        )
+        nexts.push({
+            catTx: catTx,
+            contract: contractIns.contract,
+            state: nextState,
+            contractTaproot: contractIns.contractTaproot,
+            atOutputIndex: atOutputIndex,
+        })
+    }
+    if (nextLocalId2 < max) {
+        const nextState = NftParallelClosedMinterProto.create(
+            contractIns.state.nftScript,
+            nextLocalId2
+        )
+        const atOutputIndex = catTx.addStateContractOutput(
+            contractIns.contractTaproot.lockingScript,
+            NftParallelClosedMinterProto.toByteString(nextState)
+        )
+        nexts.push({
+            catTx: catTx,
+            contract: contractIns.contract,
+            state: nextState,
+            contractTaproot: contractIns.contractTaproot,
+            atOutputIndex: atOutputIndex,
+        })
+    }
+    const atOutputIndex = catTx.addStateContractOutput(
+        contractIns.state.nftScript,
+        CAT721Proto.toByteString(nftState)
+    )
+    nexts.push({
+        catTx: catTx,
+        preCatTx: contractIns.catTx,
+        contract: nftTaproot.contract,
+        state: nftState,
+        contractTaproot: nftTaproot,
+        atOutputIndex: atOutputIndex,
+    })
+    return {
+        catTx: catTx,
+        contract: contractIns.contract,
+        state: contractIns.state,
+        contractTaproot: contractIns.contractTaproot,
+        atInputIndex: atInputIndex,
+        nexts: nexts,
+    }
+}
diff --git a/packages/smartcontracts/tests/nft/parallelclosedMinter.test.ts b/packages/smartcontracts/tests/nft/parallelclosedMinter.test.ts
new file mode 100644
index 0000000..adb0418
--- /dev/null
+++ b/packages/smartcontracts/tests/nft/parallelclosedMinter.test.ts
@@ -0,0 +1,424 @@
+import * as dotenv from 'dotenv'
+dotenv.config()
+import { expect, use } from 'chai'
+import { NftParallelClosedMinter } from '../../src/contracts/nft/nftParallelClosedMinter'
+import chaiAsPromised from 'chai-as-promised'
+import { MethodCallOptions, hash160, toByteString } from 'scrypt-ts'
+import { getOutpointString } from '../../src/lib/txTools'
+import {
+    getDummyGenesisTx,
+    getDummySigner,
+    getDummyUTXO,
+} from '../utils/txHelper'
+import { getKeyInfoFromWif, getPrivKey } from '../utils/privateKey'
+import {
+    nftParallelClosedMinterCall,
+    nftParallelClosedMinterDeploy,
+} from './parallelClosedMinter'
+import {
+    CatTx,
+    ContractCallResult,
+    ContractIns,
+    TaprootSmartContract,
+} from '../../src/lib/catTx'
+import { getBackTraceInfo } from '../../src/lib/proof'
+import { unlockTaprootContractInput } from '../utils/contractUtils'
+import { btc } from '../../src/lib/btc'
+import {
+    NftParallelClosedMinterProto,
+    NftParallelClosedMinterState,
+} from '../../src/contracts/nft/nftParallelClosedMinterProto'
+import { CAT721Proto, CAT721State } from '../../src/contracts/nft/cat721Proto'
+use(chaiAsPromised)
+
+const DUST = toByteString('4a01000000000000')
+
+export async function closedMinterUnlock<T>(
+    callInfo: ContractCallResult<T>,
+    preCatTx: CatTx,
+    seckey,
+    nftState,
+    preNftClosedMinterState,
+    pubkeyX,
+    pubKeyPrefix,
+    prePreTx,
+    options: {
+        errorSig?: boolean
+    } = {}
+) {
+    const { shPreimage, prevoutsCtx, spentScripts, sighash } =
+        callInfo.catTx.getInputCtx(
+            callInfo.atInputIndex,
+            callInfo.contractTaproot.tapleafBuffer
+        )
+    const backtraceInfo = getBackTraceInfo(
+        // pre
+        preCatTx.tx,
+        prePreTx,
+        callInfo.atInputIndex
+    )
+    const sig = btc.crypto.Schnorr.sign(seckey, sighash.hash)
+    await callInfo.contract.connect(getDummySigner())
+    const closedMinterFuncCall = await callInfo.contract.methods.mint(
+        callInfo.catTx.state.stateHashList,
+        nftState,
+        pubKeyPrefix,
+        pubkeyX,
+        () => (options.errorSig ? toByteString('') : sig.toString('hex')),
+        DUST,
+        DUST,
+        // pre state
+        preNftClosedMinterState,
+        preCatTx.getPreState(),
+        //
+        backtraceInfo,
+        shPreimage,
+        prevoutsCtx,
+        spentScripts,
+        {
+            script: toByteString(''),
+            satoshis: toByteString('0000000000000000'),
+        },
+        {
+            fromUTXO: getDummyUTXO(),
+            verify: false,
+            exec: false,
+        } as MethodCallOptions<NftParallelClosedMinter>
+    )
+    unlockTaprootContractInput(
+        closedMinterFuncCall,
+        callInfo.contractTaproot,
+        callInfo.catTx.tx,
+        // pre tx
+        preCatTx.tx,
+        callInfo.atInputIndex,
+        true,
+        true
+    )
+}
+
+// keyInfo
+const keyInfo = getKeyInfoFromWif(getPrivKey())
+const { addr: addrP2WPKH, seckey, xAddress, pubKeyPrefix, pubkeyX } = keyInfo
+const { genesisTx, genesisUtxo } = getDummyGenesisTx(seckey, addrP2WPKH)
+const genesisOutpoint = getOutpointString(genesisTx, 0)
+const nftScript =
+    '5120c4043a44196c410dba2d7c9288869727227e8fcec717f73650c8ceadc90877cd'
+
+describe('Test SmartContract `NftParallelClosedMinter`', () => {
+    let nftClosedMinter: NftParallelClosedMinter
+    let nftClosedMinterTaproot: TaprootSmartContract
+    let initNftClosedMinterState: NftParallelClosedMinterState
+    let nftClosedMinterState: NftParallelClosedMinterState
+    let contractIns: ContractIns<NftParallelClosedMinterState>
+    const collectionMax = 100n
+    before(async () => {
+        await NftParallelClosedMinter.loadArtifact()
+        nftClosedMinter = new NftParallelClosedMinter(
+            xAddress,
+            genesisOutpoint,
+            collectionMax
+        )
+        nftClosedMinterTaproot = TaprootSmartContract.create(nftClosedMinter)
+        initNftClosedMinterState = NftParallelClosedMinterProto.create(
+            nftScript,
+            0n
+        )
+        nftClosedMinterState = initNftClosedMinterState
+        contractIns = await nftParallelClosedMinterDeploy(
+            seckey,
+            genesisUtxo,
+            nftClosedMinter,
+            nftClosedMinterTaproot,
+            initNftClosedMinterState
+        )
+    })
+
+    it('should admin mint nft pass.', async () => {
+        // tx call
+        // nft state
+        const nftState = CAT721Proto.create(
+            hash160(toByteString('00')),
+            nftClosedMinterState.nextLocalId
+        )
+        const callInfo = await nftParallelClosedMinterCall(
+            contractIns,
+            nftClosedMinterTaproot,
+            nftState,
+            collectionMax
+        )
+        await closedMinterUnlock(
+            callInfo,
+            contractIns.catTx,
+            seckey,
+            nftState,
+            contractIns.state,
+            pubkeyX,
+            pubKeyPrefix,
+            genesisTx
+        )
+        expect(callInfo.nexts.length).to.be.equal(3)
+    })
+
+    it('should admin mint nft until end.', async () => {
+        // tx call
+        const nftList: ContractIns<CAT721State>[] = []
+        const parallelMinter = async function (
+            contractIns: ContractIns<NftParallelClosedMinterState>,
+            prePreTx
+        ) {
+            const nftState = CAT721Proto.create(
+                hash160(toByteString('00')),
+                contractIns.state.nextLocalId
+            )
+            const callInfo = await nftParallelClosedMinterCall(
+                contractIns,
+                nftClosedMinterTaproot,
+                nftState,
+                collectionMax
+            )
+            await closedMinterUnlock(
+                callInfo,
+                contractIns.catTx,
+                seckey,
+                nftState,
+                contractIns.state,
+                pubkeyX,
+                pubKeyPrefix,
+                prePreTx
+            )
+            nftList.push(
+                callInfo.nexts[
+                    callInfo.nexts.length - 1
+                ] as ContractIns<CAT721State>
+            )
+            if (callInfo.nexts.length > 1) {
+                for (
+                    let index = 0;
+                    index < callInfo.nexts.length - 1;
+                    index++
+                ) {
+                    await parallelMinter(
+                        callInfo.nexts[
+                            index
+                        ] as ContractIns<NftParallelClosedMinterState>,
+                        contractIns.catTx.tx
+                    )
+                }
+            }
+        }
+        await parallelMinter(contractIns, genesisTx)
+        const localIdSet = new Set(nftList.map((nft) => nft.state.localId))
+        expect(localIdSet.size).to.be.equal(Number(collectionMax))
+    })
+
+    it('should failed mint nft with error localId', async () => {
+        // tx call
+        const nftList: ContractIns<CAT721State>[] = []
+        const parallelMinter = async function (
+            contractIns: ContractIns<NftParallelClosedMinterState>,
+            prePreTx
+        ) {
+            const nftState = CAT721Proto.create(
+                hash160(toByteString('00')),
+                contractIns.state.nextLocalId + 1n
+            )
+            const callInfo = await nftParallelClosedMinterCall(
+                contractIns,
+                nftClosedMinterTaproot,
+                nftState,
+                collectionMax
+            )
+            await expect(
+                closedMinterUnlock(
+                    callInfo,
+                    contractIns.catTx,
+                    seckey,
+                    nftState,
+                    contractIns.state,
+                    pubkeyX,
+                    pubKeyPrefix,
+                    prePreTx
+                )
+            ).to.be.rejected
+            nftList.push(
+                callInfo.nexts[
+                    callInfo.nexts.length - 1
+                ] as ContractIns<CAT721State>
+            )
+            if (callInfo.nexts.length > 1) {
+                for (
+                    let index = 0;
+                    index < callInfo.nexts.length - 1;
+                    index++
+                ) {
+                    await parallelMinter(
+                        callInfo.nexts[
+                            index
+                        ] as ContractIns<NftParallelClosedMinterState>,
+                        contractIns.catTx.tx
+                    )
+                }
+            }
+        }
+        await parallelMinter(contractIns, genesisTx)
+        expect(nftList).to.be.length(Number(collectionMax))
+    })
+
+    it('should failed mint nft with error nextLocalId', async () => {
+        it('should admin mint nft until end.', async () => {
+            // tx call
+            const nftList: ContractIns<CAT721State>[] = []
+            const parallelMinter = async function (
+                contractIns: ContractIns<NftParallelClosedMinterState>,
+                prePreTx
+            ) {
+                const nftState = CAT721Proto.create(
+                    hash160(toByteString('00')),
+                    contractIns.state.nextLocalId
+                )
+                const callInfo = await nftParallelClosedMinterCall(
+                    contractIns,
+                    nftClosedMinterTaproot,
+                    nftState,
+                    collectionMax,
+                    true
+                )
+                await expect(
+                    closedMinterUnlock(
+                        callInfo,
+                        contractIns.catTx,
+                        seckey,
+                        nftState,
+                        contractIns.state,
+                        pubkeyX,
+                        pubKeyPrefix,
+                        prePreTx
+                    )
+                ).to.be.rejected
+                nftList.push(
+                    callInfo.nexts[
+                        callInfo.nexts.length - 1
+                    ] as ContractIns<CAT721State>
+                )
+                if (callInfo.nexts.length > 1) {
+                    for (
+                        let index = 0;
+                        index < callInfo.nexts.length - 1;
+                        index++
+                    ) {
+                        await parallelMinter(
+                            callInfo.nexts[
+                                index
+                            ] as ContractIns<NftParallelClosedMinterState>,
+                            contractIns.catTx.tx
+                        )
+                    }
+                }
+            }
+            await parallelMinter(contractIns, genesisTx)
+            expect(nftList).to.be.length(Number(collectionMax))
+        })
+    })
+
+    it('should failed mint nft with error sig', async () => {
+        // tx call
+        let prePreTx = genesisTx
+        while (nftClosedMinterState.nextLocalId <= collectionMax) {
+            // nft state
+            const nftState = CAT721Proto.create(
+                hash160(toByteString('00')),
+                nftClosedMinterState.nextLocalId
+            )
+            const callInfo = await nftParallelClosedMinterCall(
+                contractIns,
+                nftClosedMinterTaproot,
+                nftState,
+                collectionMax
+            )
+            await expect(
+                closedMinterUnlock(
+                    callInfo,
+                    contractIns.catTx,
+                    seckey,
+                    nftState,
+                    contractIns.state,
+                    pubkeyX,
+                    pubKeyPrefix,
+                    prePreTx,
+                    {
+                        errorSig: true,
+                    }
+                )
+            ).to.be.rejected
+            prePreTx = contractIns.catTx.tx
+            if (callInfo.nexts.length > 1) {
+                contractIns = callInfo
+                    .nexts[0] as ContractIns<NftParallelClosedMinterState>
+            } else {
+                break
+            }
+            nftClosedMinterState.nextLocalId += 1n
+        }
+    })
+
+    it('should failed genesis tx more than one minter', async () => {
+        async function nftParallelClosedMinterDeploy(
+            seckey,
+            genesisUtxo,
+            nftClosedMinter: NftParallelClosedMinter,
+            nftClosedMinterTaproot: TaprootSmartContract,
+            nftClosedMinterState: NftParallelClosedMinterState
+        ): Promise<ContractIns<NftParallelClosedMinterState>> {
+            // tx deploy
+            const catTx = CatTx.create()
+            catTx.tx.from([genesisUtxo])
+            const atIndex = catTx.addStateContractOutput(
+                nftClosedMinterTaproot.lockingScript,
+                NftParallelClosedMinterProto.toByteString(nftClosedMinterState)
+            )
+            catTx.addStateContractOutput(
+                nftClosedMinterTaproot.lockingScript,
+                NftParallelClosedMinterProto.toByteString(nftClosedMinterState)
+            )
+            catTx.sign(seckey)
+            return {
+                catTx: catTx,
+                contract: nftClosedMinter,
+                state: nftClosedMinterState,
+                contractTaproot: nftClosedMinterTaproot,
+                atOutputIndex: atIndex,
+            }
+        }
+        const contractIns = await nftParallelClosedMinterDeploy(
+            seckey,
+            genesisUtxo,
+            nftClosedMinter,
+            nftClosedMinterTaproot,
+            initNftClosedMinterState
+        )
+        const nftState = CAT721Proto.create(
+            hash160(toByteString('00')),
+            nftClosedMinterState.nextLocalId
+        )
+        const callInfo = await nftParallelClosedMinterCall(
+            contractIns,
+            nftClosedMinterTaproot,
+            nftState,
+            collectionMax
+        )
+        await expect(
+            closedMinterUnlock(
+                callInfo,
+                contractIns.catTx,
+                seckey,
+                nftState,
+                contractIns.state,
+                pubkeyX,
+                pubKeyPrefix,
+                genesisTx
+            )
+        ).to.be.rejected
+        expect(callInfo.nexts.length).to.be.equal(3)
+    })
+})