@flop-labs/sdk

中文文档
0.6.0发布于 2026/11/12MIT OR Apache-2.0TypeScriptESM + CJS近 30 天下载 3449

TypeScript SDK for the FLOP Network – HTLC workflows, PoUI proofs, and account management

原始使用文档
文档目录20 节
  1. Installation
  2. Peer Dependencies
  3. Quick Start
  4. API Reference
  5. FlopClient
  6. Agent Wallet
  7. x402 Payment Protocol
  8. Named-miner sessions
  9. Session Estimation (compute channel)
  10. React Hooks (Optional)
  11. Types
  12. Error Handling
  13. Complete Example
  14. Development
  15. License
  16. NVIDIA GPU Operators
  17. TEE Quote Format
  18. Supported TEE Types
  19. TEE Verification
  20. Links

@flop-labs/sdk

FaucetClient.healthCheck() reports the faucet's HTTP readiness as a boolean. It returns false for HTTP 503 or a request failure, including a live faucet whose caller IP classification has not been checked. This is not a process-liveness or grant-eligibility test. The health GET does not request funds or classify the IP. An explicit requestFaucetFunding() attempt performs admission and returns a refusal reason and retryAfterSeconds when supplied. It never automatically repeats a funding POST. An unknown outcome can mean a grant occurred: preserve the result and reconcile before requesting again. The health check alone must not block an explicit funding request.

Unreleased faucet migration: fundFromFaucet retains its boolean signature but currently always returns false; false means unconfirmed, not unpaid. The faucet does not yet expose an authenticated durable binding from a unique request to its transaction, so the SDK cannot establish request-specific confirmation. Use fundFromFaucetDetailed to distinguish refusal, unavailable preflight, and unknown outcomes and retain the returned hash when available. Its accepted HTTP response is still unknown as a grant. QuickStart throws FaucetUnconfirmedError with the outcome when funding cannot be confirmed; reconcile the original request before retrying. Faucet-enabled QuickStart requires a caller-owned signer or keypair so an unconfirmed payment cannot be stranded in an inaccessible generated wallet. For the separate question of whether an account has enough funds, use waitForAvailableBalance; the old waitForFaucetFunding name is a deprecated balance-check alias. No source-only test confirms a live grant.

Ordinary HTLC creation requires a registered EconomicCritical appraisal profile. Supply tee_requirements / teeRequirements as {profile: {profile_id, version}, model_hash, measurement_hash} (32-byte hex hashes), or its canonical 100-byte SCALE hex. The runtime checks that the registered profile and immutable commitments match its policy; there is no simulator default. For the examples, set FLOP_HTLC_TEE_REQUIREMENTS to that canonical hex from your network's registered profile.

TypeScript SDK for the FLOP Network — HTLC workflows, PoI proofs, and account management.

SUPPORTED_SPEC_VERSIONS in this source admits exactly 1013000, 1014000, 1014001, 1014002, 1014003, 1014004, 1014005, 1014007, 1014008, 1014009, 1014010, 1014011, 1014012, 1014013, 1014014, 1014015, 1014016, 1014017, 1014018, 1014019, 1014020. SUPPORTED_SPEC_VERSION_RANGE retains its exported shape as the envelope 1013000..1014020; admission requires exact membership (including no 1014006). The published 0.4.0 executable stops at 1014011 and also excludes 1014006; its bundled README has stale connection and HTLC examples. These source corrections are unreleased: installing @latest does not deliver them until a new version is published. The examples below describe this source candidate. Qualification covers runtime 1013000 at 44a5fedc88614b9a6e8c97605c2ec943faf47d9d, the registrar runtime 1014000 at e4f01cc849fa6c768d5c0f9c111dffb450ea4fa9 (PR #2726), runtime 1014001 at de4e2779dbf6f8d25348953ce0992ef8e89756bd (PR #2803), runtime 1014002, the #2637 devnet hybrid-verifier fast path, and runtime 1014003 at 9035450361681a69c6c02c9c53e47351999ef6f8 (PR #3096). Candidate runtime 1014004 (1.14.4, PR #3006) has source compatibility coverage for uploader-funded qualification. Candidate runtime 1014005 (1.14.5) has source compatibility coverage for the preactivation FCU name replacement; this does not qualify physical latency or deployment. Candidate specs beyond 1014003 have source compatibility coverage; acceptance by the SDK does not establish deployment or end-to-end inference qualification. Other versions require separate qualification. Every FlopClient connection path checks it. Unsupported runtime upgrades detected by polkadot.js metadata events disconnect the API and make subsequent SDK operations raise FlopError with code RUNTIME_INCOMPATIBLE, the qualified versions, and upgrade guidance. Generated clients apply the same exact-version guard; explicit profile constraints can narrow compatibility and require a spec name. Upgrade with pnpm add @flop-labs/sdk@latest to a release supporting the chain's runtime; allowlist changes require compatibility validation.

Installation

npm install @flop-labs/sdk

Peer Dependencies

React is optional (only needed if using React hooks):

npm install @flop-labs/sdk react  # Optional: for React integration

Quick Start

The live devnet was checked at finalized block 2,764,673 on 3 November 2026: spec 1014003, with an empty attest.profiles registry. The HTLC example below cannot run there until authorized governance registers its exact EconomicCritical appraisal policy, verifier set and version. Neither simulator mode nor an arbitrary profile ID supplies that prerequisite. Use assertHtlcAppraisalProfile(api, hex) before signing; the runtime independently enforces the policy and commitments. The three exported ghost/TOPLOC/DA profile references are not HTLC profiles.

For a direct compute-channel purchase, prepare and persist the original intent:

import { FlopClient } from '@flop-labs/sdk';
import { readFile, writeFile } from 'node:fs/promises';

const client = await FlopClient.createWithKeypair('wss://rpc.devnet.flop.network', keypair);
try {
  // Run preparation once, in a private directory. The intent contains a session seed.
  const intent = await client.prepareInferenceSession({ minerUrl, escrowFlop: 0.01, maxTurns: 1 });
  await writeFile('purchase.json', JSON.stringify(intent), { flag: 'wx', mode: 0o600 });
  const saved = JSON.parse(await readFile('purchase.json', 'utf8'));
  const result = await client.buyInferenceSession({ intent: saved, prompt: 'Hello' });
  console.log(result.channelId, result.status);
} finally { await client.disconnect(); }

On restart, read the existing file and pass that intent; never run preparation again for the same purchase. Hold exclusive ownership of the file while using it. Alternatively, buyInferenceSession accepts an awaited onPrepared persistence callback. Calls without an intent or callback stop before signing. This tightens the former convenience API and needs a breaking-change release. The SDK reads finalized state before submitting. A missing record is still an unconfirmed outcome; resubmission retains the original channel nonce and key. An existing channel stops with INFERENCE_PURCHASE_UNCONFIRMED and its public ID. Even Open may have streamed off-chain turns: do not replay them with fresh counters. Inspect settlement or recover the retained transcript; if an undelivered Open channel exceeds its SLA, use the permissionless timeout path after confirming eligibility. Retain the intent until recovery and settlement/refund are finalized. Repeated extrinsics may still pay fees. settlement_posted opens a challenge window and does not establish a finalized miner payment.

import { FlopClient, createAgentWallet } from '@flop-labs/sdk';

// Connect to FLOP node
const client = await FlopClient.create({ endpoint: 'ws://127.0.0.1:9944', signer: '//Alice' });

// Check node health
const health = await client.health();
console.log('Node healthy:', health);

// Create an agent wallet
const wallet = await createAgentWallet(client.api, client.signer, {
  agent: client.address,
  dailyCap: 100n * 10n ** 18n,
  perTxLimit: 5n * 10n ** 18n,
});

// Create HTLC for inference purchase
const htlc = await client.createHtlc({
  agent: 'my-agent',
  task: {
    model: 'llama-3-8b',
    prompt: 'Hello, world!',
    tee_requirements: process.env.FLOP_HTLC_TEE_REQUIREMENTS
  },
  rewardFlop: 5.0,
  timeoutBlocks: 60
});

console.log('HTLC created:', htlc.htlcId);

// Wait for settlement
const status = await client.waitForSettlement(htlc.hashLock);
console.log('Settlement:', status);

API Reference

FlopClient

Main client for interacting with the FLOP Network.

Connection factories

await FlopClient.create(options?: FlopClientOptions)

Options:

  • endpoint (string, optional): RPC endpoint URL. Default: ws://127.0.0.1:9944
  • signer (string, optional): local signer URI; the //Alice default is for disposable local development only.
  • createWithKeypair(endpoint, keypair): connect using an existing decoded keypair.

Methods

health()

Check node health status.

await client.health();
// Returns: { isSyncing: boolean, peers: number, shouldHavePeers: boolean }
createHtlc(params)

Create a Hash Time Lock Contract for atomic inference purchase.

await client.createHtlc({
  agent: string,
  task: ComputeTask,
  rewardFlop: number,
  timeoutBlocks: number,
  hashLock?: string  // Auto-generated if not provided
});

Returns:

{
  htlcId: string;
  hashLock: string;
  preImage: string;
  txHash: string;
}
createCrossChainHtlc(params)

Create a cross-chain HTLC for a FLOP ↔ BTC atomic swap (bilateral HTLC path). The FLOP leg must outlive the counter-chain leg (timelock symmetry, enforced on-chain), so timeoutBlocks is the longer leg and btcTimelockHours the shorter. USDT on TON/TRON settles via NEAR Intents instead — see docs/bridge/settlement.md.

await client.createCrossChainHtlc({
  agent: string,
  task: ComputeTask,
  rewardFlop: number,
  btcAddress: string,
  btcAmountSats: number | bigint,
  timeoutBlocks?: number,    // FLOP side, default 7200 (~2h)
  btcTimelockHours?: number, // counter-chain side, default 1
  hashLock?: string          // auto-generated if omitted
});
checkHtlcStatus(hashLock)

Query HTLC status by hash lock.

await client.checkHtlcStatus('0x...');
// Returns: HtlcInfo | null (RPC fields include reward_amount and timeout_block)
waitForSettlement(hashLock, timeoutMs?, pollIntervalMs?)

Poll HTLC until settled or timeout.

await client.waitForSettlement('0x...', 30_000, 1_000);
// Returns: HtlcInfo with status SETTLED or REFUNDED, or null on polling timeout.
// null does not refund funds; reconcile the original HTLC before retrying.
getBalance(address?)

Get account balance in FLOP tokens.

await client.getBalance(); // Defaults to the connected account's SS58 address.
// Returns: string (human-readable FLOP, preserving decimal precision)
submitPoUI(proofs, gnWeight, taskHash)

Submit TEE-verified PoI proof batch.

await client.submitPoUI(proofs, 1450, '0x...');
getAccountInfo(address)

Get account information including nonce and metadata.

await client.getAccountInfo('flop1qyq...');
getComputeTask(taskId)

Get compute task details.

await client.getComputeTask('task_0x...');
getTeeQuote(task)

Get TEE quote for compute task.

await client.getTeeQuote({ model: 'llama-3-8b', prompt: '...' });

Agent Wallet

Create and manage agent wallets with session keys.

createAgentWallet(api, signer, options)

Create a new agent wallet.

import { createAgentWallet } from '@flop-labs/sdk';

const walletTxHash = await createAgentWallet(client.api, client.signer, {
  agent: agentAddress, // Existing agent SS58 account, distinct from the owner.
  dailyCap: 100n * 10n ** 18n,
  perTxLimit: 5n * 10n ** 18n,
});
// Returns the transaction hash, not a wallet object.

fundAgentWallet(api, signer, agent, amountRaw)

Fund an agent wallet from owner account.

import { fundAgentWallet } from '@flop-labs/sdk';

await fundAgentWallet(client.api, client.signer, agentAddress, 50n * 10n ** 18n);

agentTransfer(client, fromWallet, toAddress, amount)

Transfer funds between agent wallets.

import { agentTransfer } from '@flop-labs/sdk';

await agentTransfer(client, 'flop1agent1...', 'flop1agent2...', 10.0);

ownerWithdraw(client, walletAddress, amount)

Withdraw funds from agent wallet to owner account.

import { ownerWithdraw } from '@flop-labs/sdk';

await ownerWithdraw(client, 'flop1agent...', 25.0);

createSession(client, walletAddress, options)

Create a session key with spending limits.

import { createSession } from '@flop-labs/sdk';

const session = await createSession(client, 'flop1agent...', {
  spendingLimit: 10.0,
  expiresAt: Date.now() + 3600000  // 1 hour
});

executeViaSession(client, sessionKey, call)

Execute a call using session key.

import { executeViaSession } from '@flop-labs/sdk';

await executeViaSession(client, sessionKey, {
  pallet: 'htlc',
  method: 'create',
  args: { ... }
});

x402 Payment Protocol

Utilities for x402 payment headers.

x402Fetch(url, options, paymentParams)

Fetch with x402 payment headers.

import { x402Fetch } from '@flop-labs/sdk';

const response = await x402Fetch('https://api.example.com/inference', {
  method: 'POST',
  body: JSON.stringify({ prompt: '...' })
}, {
  amount: 5.0,
  recipient: 'flop1qyq...'
});

parsePaymentHeaders(headers)

Parse x402 payment headers from response.

import { parsePaymentHeaders } from '@flop-labs/sdk';

const payment = parsePaymentHeaders(response.headers);

Named-miner sessions

runDirectSession({ api, signer, miner, model, endpoint, prompt, maxFlop, maxTokens }) opens a channel on the named miner, verifies its identity and signed response, and settles or recovers escrow. maxFlop is raw chain units; maxTokens is a positive safe integer and defaults to 256 completion tokens. The request carries that limit in the chat envelope bound by the verified receipt. Inspect receipts[0].finishReason and completionTokens for truncation. The 60-second turn deadline remains in force; larger token budgets do not extend it. Settlement separately waits for the channel's finalized calibration allowance before submitting payment. Cooperative delivery and finalized-event discovery each have a bounded 60-second window. Expired channels use timeout recovery. If the result is unknown, retain the channel ID and inspect finalized channel state and terminal events before retrying; a closed channel alone does not prove successful settlement. A zero-turn force-open can be expired after its acknowledgement deadline, and an open channel can be timed out after its lifetime.

Session Estimation (compute channel)

Size a compute-channel session before opening it, entirely off chain, using the chain's own meter:

  • computeChannel.estimateSession turns a turn profile into G_n, minimum blocks, channel count and escrow.
  • computeChannel.sessionPrior returns P50/P90/P99 profiles for a task you have not run yet.
  • computeChannel.calibrateSessionPrior fits that prior to your model and agent configuration from about 10 probe runs.
  • computeChannel.reforecastSession updates the quantiles mid-run.

See the session estimation guide.

React Hooks (Optional)

import { useFlopClient, useWalletBalance, useHtlcStatus } from '@flop-labs/sdk/react';

function MyComponent() {
  const client = useFlopClient('http://127.0.0.1:16111/rpc');
  const { balance } = useWalletBalance(client, 'flop1qyq...');
  const { status } = useHtlcStatus(client, '0x...');
  
  return <div>Balance: {balance} FLOP</div>;
}

Types

interface ComputeTask {
  model: string;
  prompt: string;
  maxTokens?: number;
  temperature?: number;
}

type HtlcInfo = import('@flop-labs/sdk').HtlcInfo;

interface PoUISubmission {
  proofHash: string;
  gnMeasured: number;
  teeQuote: string;
  timestamp: number;
}

interface WalletInfo {
  address: string;
  balance: number;
  dailyLimit: number;
  dailySpent: number;
  allowedPallets: string[];
}

Error Handling

import { FlopClient, FlopError } from '@flop-labs/sdk';

let client: FlopClient | undefined;
try {
  client = await FlopClient.create({ endpoint: 'ws://127.0.0.1:9944', signer: '//Alice' });
  console.log(await client.health());
} catch (error: unknown) {
  if (error instanceof FlopError) {
    // Includes RUNTIME_INCOMPATIBLE raised while awaiting the connection factory.
    console.error(error.code, error.message);
  } else if (error instanceof Error) {
    console.error(error.message);
  } else {
    console.error('Unexpected error:', error);
  }
} finally {
  await client?.disconnect();
}

Complete Example

This source example uses the public, well-known //Alice key only on a disposable local chain. Set FLOP_AGENT_ADDRESS to the agent's SS58 account and FLOP_HTLC_TEE_REQUIREMENTS to the registered appraisal requirements matching the model used below. Use a private working directory. The HTLC recovery file is created exclusively before transactions; retain it on errors and reconcile its original hash lock before resuming, rather than rerunning this one-time setup. Never log its pre-image.

import { writeFile } from 'node:fs/promises';
import { FlopClient, assertHtlcAppraisalProfile, createAgentWallet, fundAgentWallet } from '@flop-labs/sdk';

async function main() {
  const agentAddress = process.env.FLOP_AGENT_ADDRESS;
  const teeRequirements = process.env.FLOP_HTLC_TEE_REQUIREMENTS;
  if (!agentAddress || !teeRequirements) throw new Error('Set the agent address and registered HTLC appraisal requirements');
  const client = await FlopClient.create({ endpoint: 'ws://127.0.0.1:9944', signer: '//Alice' });
  try {
    await assertHtlcAppraisalProfile(client.api, teeRequirements);
    const { preImage, hashLock } = FlopClient.generateSecrets();
    await writeFile('htlc-purchase.json', JSON.stringify({ preImage, hashLock }) + '\n', { flag: 'wx', mode: 0o600 });

    const FLOP = 10n ** 18n;
    const walletTxHash = await createAgentWallet(client.api, client.signer, {
      agent: agentAddress,
      dailyCap: 100n * FLOP,
      perTxLimit: 5n * FLOP,
    });
    console.log('Wallet creation transaction:', walletTxHash);
    await fundAgentWallet(client.api, client.signer, agentAddress, 50n * FLOP);

    // This direct HTLC uses the owner's balance, separately from the agent wallet.
    const htlc = await client.createHtlc({
      agent: agentAddress,
      task: { model: 'llama-3-8b', prompt: 'Analyze BTC price trend', tee_requirements: teeRequirements },
      rewardFlop: 5.0,
      timeoutBlocks: 60,
      hashLock,
    });
    console.log('HTLC created:', htlc.htlcId);

    const settlement = await client.waitForSettlement(hashLock, 30_000, 1_000);
    if (settlement?.status === 'SETTLED') {
      console.log('Node reports settlement:', settlement.miner, settlement.gn_used);
    } else if (settlement?.status === 'REFUNDED') {
      console.log('Node reports a refund; confirm finality before treating it as complete');
    } else {
      console.log('Unconfirmed: polling timed out without a refund. Keep the original intent and reconcile chain state');
    }
  } finally {
    await client.disconnect();
  }
}

main().catch(console.error);

Development

# Install dependencies
npm install

# Build the SDK
npm run build

# Watch mode
npm run dev

# Clean build artifacts
npm clean

License

Apache-2.0 © FLOP Network

NVIDIA GPU Operators

For miners operating NVIDIA GPUs to earn rewards, see the complete guide:

Quick example for GPU operators:

import { FlopClient, type TeeQuote } from '@flop-labs/sdk';

// Connect as miner
const client = await FlopClient.create({ 
  endpoint: 'ws://127.0.0.1:9944',
  signer: '//Miner'
});

// Monitor for HTLCs
const htlc = await client.checkHtlcStatus(hashLock);
if (htlc && htlc.status === 'CREATED') {
  // Execute inference on NVIDIA GPU
  const result = await executeOnGpu(htlc.task); // Your GPU execution logic
  
  // Generate TEE quote
  const teeQuote: TeeQuote = {
    version: '1.0',
    miner_address: client.address,
    task_reference: {
      htlc_hash_lock: htlc.hashLock,
      model_hash: `sha256:${htlc.task.model}`,
      task_timestamp: Math.floor(Date.now() / 1000),
    },
    compute_output: {
      gn_weight: result.gnWeight,  // G_n weight (BFLOPs)
      execution_time_ms: result.executionTime,
      encrypted_result: result.encryptedOutput,
    },
    attestation: {
      tee_type: 'NVIDIA_HOPPER_CC',  // or 'NVIDIA_RUBIN_CC', 'CPU_WRAPPED_TEE'
      hardware_pubkey: hardwarePubkey,
      quote_hex: teeQuoteHex,
      signature: minerSignature,
    },
  };
  
  // Redeem HTLC with TEE quote
  const txHash = await client.redeemHtlc(
    htlc.htlcId,
    preImage,
    teeQuote,
  );
  console.log('✓ HTLC redeemed:', txHash);
  
  // Submit PoUI proof batch (optional, for additional rewards)
  const proofs = generateStarkProofs(result);
  const taskHash = generateTaskHash(htlc);
  const proofTxHash = await client.submitPoUI(proofs, result.gnWeight, taskHash);
  console.log('✓ PoUI proof submitted:', proofTxHash);
}

TEE Quote Format

interface TeeQuote {
  version: string;           // "1.0"
  miner_address: string;     // flop1... address
  task_reference: {
    htlc_hash_lock: string;  // HTLC identifier
    model_hash: string;      // sha256:model...
    task_timestamp: number;  // Unix timestamp (seconds)
  };
  compute_output: {
    gn_weight: number;       // G_n weight (BFLOPs)
    execution_time_ms: number;
    encrypted_result: string; // 0x... encrypted output
  };
  attestation: {
    tee_type: string;        // "NVIDIA_HOPPER_CC" | "NVIDIA_RUBIN_CC" | "CPU_WRAPPED_TEE"
    hardware_pubkey: string; // 0x... hardware public key
    quote_hex: string;       // 0x... raw TEE quote
    signature: string;       // 0x... miner signature
  };
}

Supported TEE Types

TEE TypeHardwareProduction Ready
NVIDIA_HOPPER_CCH100, H200✅ Yes
NVIDIA_RUBIN_CCB100, B200✅ Yes
CPU_WRAPPED_TEERTX 4090+ + AMD SEV/Intel TDX⚠️ Testnet only

TEE Verification

The TEE bridge validates quotes with:

  • Timestamp freshness: Quotes must be < 60 seconds old
  • Signature verification: Validates miner signature
  • Replay protection: Tracks task hashes to prevent double-spending
  • Hardware verification: Checks hardware keys against registry (production only)
import { verifyTeeQuote, type PouiPayload } from '@flop-labs/sdk/tee';

const bridge = new TeeBridge({ simulatorMode: false });

// Register known hardware keys (production)
bridge.registerHardwareKey('0x...');

// Verify TEE quote
const payload: PouiPayload = parseTeeQuote(quoteJson);
const result = await bridge.verify(payload);

if (result.valid) {
  console.log(`✓ TEE verified: ${result.gnWeight} G_n`);
} else {
  console.error(`✗ Verification failed: ${result.error}`);
}

For complete setup instructions, hardware requirements, and production deployment guide:

版本与发布记录

@flop-labs/sdk 共发布 5 个版本,首次发布于 2026/10/29;过去 12 个月发布了 5 个版本。

版本发布日期距今
0.6.0latest2026/11/123 天前
0.5.22026/11/105 天前
0.5.12026/11/097 天前
0.4.02026/10/2917 天前
0.0.0-stage2026/10/2917 天前

关于 @flop-labs/sdk 的常见问题

@flop-labs/sdk 是什么?

TypeScript SDK for the FLOP Network – HTLC workflows, PoUI proofs, and account management

如何安装 @flop-labs/sdk?

在项目目录运行 npm install @flop-labs/sdk,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 flop-client-gen,可以用 npx flop-client-gen 直接运行。

@flop-labs/sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@flop-labs/sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@flop-labs/sdk 可以商用吗?

@flop-labs/sdk 使用 MIT OR Apache-2.0 许可。可在 MIT、Apache-2.0 中任选其一遵守。选择 MIT 时,宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@flop-labs/sdk 的下载量有多少?

根据 npm 官方下载统计,@flop-labs/sdk 在 2026/10/15 – 2026/11/13 这 30 天内被下载 3,449 次,日均约 115 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@flop-labs/sdk 还在维护吗?

最新版本 0.6.0 发布于 2026/11/12(3 天前);至今共发布 5 个版本,其中过去 12 个月发布了 5 个;首次发布于 2026/10/29。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。