mcpField
S
starknet-js

starknet-js

github.com/keep-starknet-strange開発8141 回インストール3 週間前に更新
リポジトリ

starknet-js について

starknet.js v9.x は、TypeScript で Starknet アプリを開発するための SDK です。ネットワーク接続、アカウント、コントラクト操作に加え、マルチコール、トランザクションのシミュレーション、ウォレット接続、Paymaster 操作を扱います。

主な機能

  1. 01Starknet RPC への接続
  2. 02アカウントとトランザクションの管理
  3. 03コントラクトの読み取りと呼び出し
  4. 04複数の呼び出しを一括実行
  5. 05シミュレーションと手数料見積もり
  6. 06ウォレットと Paymaster の統合

活用シーン

  1. 01Starknet のネットワークやコントラクト状態の読み取り
  2. 02コントラクトトランザクションの送信とレシート待機
  3. 03コントラクト呼び出しの一括処理
  4. 04アプリへのウォレットやガス代スポンサーの追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/keep-starknet-strange/starknet-agentic --skill starknet-js

インストール後、Claude Code で/starknet-jsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

package.json
tsconfig.json
agents/
openai.yaml
references/
account-lifecycle.md
fee-strategy.md
provider-hardening.md
README.md
scripts/
account-example.ts

詳細

starknet.js v9.x SDK

関連モジュール:スキルカタログ。

使用する場面

  • プロバイダー、アカウント、コントラクト、ウォレット、またはペイマスターのフローを使用して、Starknet アプリを構築する場合。

使用しない場面

  • Cairo コントラクトの作成、デプロイ専用の手順書、またはセキュリティ監査。

クイックスタート

npm install starknet

Starknet から読み取るための最小限のセットアップ:

import { RpcProvider, Contract } from 'starknet';

const provider = await RpcProvider.create({ nodeUrl: 'https://rpc.starknet.lava.build' });
const contract = new Contract(abi, contractAddress, provider);
const result = await contract.get_balance();

コアアーキテクチャ

Provider -> Account -> Contract
   |          |          |
Network   Identity   Interaction
  • プロバイダー:読み取り専用のネットワーク接続(RpcProvider)
  • アカウント:プロバイダーを拡張した、署名およびトランザクション機能
  • コントラクト:デプロイ済みコントラクトへの型安全なインターフェース

読み取り操作にはプロバイダーを、書き込み操作にはアカウントを使用します。

プロバイダーのセットアップ

import { RpcProvider } from 'starknet';

// Recommended: Auto-detect RPC spec version
const provider = await RpcProvider.create({
  nodeUrl: 'https://rpc.starknet.lava.build'
});

ネットワーク:

  • メインネット:https://rpc.starknet.lava.build
  • Sepolia:https://rpc.starknet-testnet.lava.build

主なメソッド:

const chainId = await provider.getChainId();
const block = await provider.getBlock('latest');
const nonce = await provider.getNonceForAddress(accountAddress);
await provider.waitForTransaction(txHash);

// Read storage directly
const value = await provider.getStorageAt(contractAddress, storageKey);

アカウント管理

アカウントの作成(4 ステップ)

ステップ 1:アドレスを計算する

import { hash, ec, encode, CallData } from 'starknet';

// IMPORTANT: `stark.randomAddress()` returns an address-like random felt and is NOT a private key.
// Use a real stark curve private key generator.
const privateKey = '0x' + encode.buf2hex(ec.starkCurve.utils.randomPrivateKey());
const publicKey = ec.starkCurve.getStarkKey(privateKey);

// NOTE: account class hashes are network/account-type dependent.
// Treat this as an example only (verify the correct class hash for your setup).
const classHash = '0x540d7f5ec7ecf317e68d48564934cb99259781b1ee3cedbbc37ec5337f8e688'; // example

const constructorCalldata = CallData.compile({ publicKey });
const address = hash.calculateContractAddressFromHash(publicKey, classHash, constructorCalldata, 0);

ステップ 2: デプロイ前に STRK をアドレスに入金します。

ステップ 3:デプロイする

import { Account } from 'starknet';

// NOTE: Account constructor signature varies across starknet.js versions.
// If this doesn't typecheck for your version, refer to the official docs.
const account = new Account({ provider, address, signer: privateKey, cairoVersion: '1' });
const { transaction_hash } = await account.deployAccount({
  classHash,
  constructorCalldata,
  addressSalt: publicKey
});
await provider.waitForTransaction(transaction_hash);

ステップ 4:アカウントを使用する してトランザクションを実行します。

既存のアカウントへの接続
const account = new Account({
  provider,
  address: '0x123...',
  signer: privateKey,
  cairoVersion: '1'  // Optional, auto-detected if omitted
});

コントラクトとのやり取り

コントラクトへの接続
import { Contract } from 'starknet';

const contract = new Contract(abi, contractAddress, provider);  // Read-only
const writeContract = new Contract(abi, contractAddress, account);   // Read-write
型付きコントラクト(型安全)
// Get full TypeScript autocomplete and type checking from ABI
const typedContract = contract.typedv2(abi);
const balance = await typedContract.balanceOf(userAddress);
状態の読み取り
const balance = await contract.get_balance();
const userBalance = await contract.balanceOf(userAddress);
書き込み(実行)
const tx = await contract.increase_balance(100);
await provider.waitForTransaction(tx.transaction_hash);
マルチコール(トランザクションの一括処理)
import { CallData, cairo } from 'starknet';

const calls = [
  {
    contractAddress: tokenAddress,
    entrypoint: 'approve',
    calldata: CallData.compile({ spender: bridgeAddress, amount: cairo.uint256(1000n) })
  },
  {
    contractAddress: bridgeAddress,
    entrypoint: 'deposit',
    calldata: CallData.compile({ amount: cairo.uint256(1000n) })
  }
];

const tx = await account.execute(calls);

型安全のために populate() を使用する:

const approveCall = tokenContract.populate('approve', {
  spender: bridgeAddress,
  amount: cairo.uint256(1000n)
});
const depositCall = bridgeContract.populate('deposit', { amount: cairo.uint256(1000n) });
const tx = await account.execute([approveCall, depositCall]);
イベントの解析
const receipt = await provider.getTransactionReceipt(txHash);
const events = contract.parseEvents(receipt);
const transferEvents = contract.parseEvents(receipt, 'Transfer');

トランザクションのシミュレーション

実行前にシミュレーションして、リバートを検出し、状態の変更を確認します:

const simResult = await account.simulateTransaction(
  [{ type: 'INVOKE', payload: calls }],
  { skipValidate: false }
);

console.log('Fee estimate:', simResult[0].fee_estimation);
console.log('Trace:', simResult[0].transaction_trace);

// Check state changes before execution
const trace = simResult[0].transaction_trace;
if (trace?.state_diff) {
  console.log('Storage changes:', trace.state_diff.storage_diffs);
}

手数料の見積もり

const fee = await account.estimateInvokeFee(calls);
console.log({
  overallFee: fee.overall_fee,
  resourceBounds: fee.resourceBounds  // V3: l1_gas, l2_gas, l1_data_gas
});

カスタム上限を指定して実行する:

const tx = await account.execute(calls, {
  resourceBounds: {
    l1_gas: { amount: '0x2000', price: '0x1000000000' },
    l2_gas: { amount: '0x0', price: '0x0' },
    l1_data_gas: { amount: '0x1000', price: '0x1000000000' }
  }
});

優先チップを付ける場合:

const tipStats = await provider.getEstimateTip();
const tx = await account.execute(calls, { tip: tipStats.percentile_75 });

トランザクションレシートの処理

const receipt = await provider.waitForTransaction(txHash);

// Status check helpers
if (receipt.isSuccess()) {
  console.log('Transaction succeeded');
} else if (receipt.isReverted()) {
  console.log('Reverted:', receipt.revert_reason);
} else if (receipt.isRejected()) {
  console.log('Rejected');
} else if (receipt.isError()) {
  console.log('Error');
}

ウォレット連携

ブラウザーウォレット(ArgentX、Braavos)に接続します:

import { connect } from '@starknet-io/get-starknet';
import { WalletAccount } from 'starknet';

const selectedWallet = await connect({ modalMode: 'alwaysAsk' });
const walletAccount = await WalletAccount.connect(
  { nodeUrl: 'https://rpc.starknet.lava.build' },
  selectedWallet
);

// Use like regular Account
const tx = await walletAccount.execute(calls);

// Event handlers
walletAccount.onAccountChange((accounts) => console.log('New account:', accounts[0]));
walletAccount.onNetworkChanged((chainId) => console.log('Network changed:', chainId));

ペイマスター(ガスのスポンサー提供)

スポンサー付き、または代替ガストークンを使用したトランザクション用にペイマスターをセットアップします:

import { PaymasterRpc, Account } from 'starknet';

const paymaster = new PaymasterRpc({ nodeUrl: 'https://sepolia.paymaster.avnu.fi' });
const account = new Account({ provider, address, signer: privateKey, paymaster });

スポンサー付き(dApp がガスを支払う):

const tx = await account.executePaymasterTransaction(calls, { feeMode: { mode: 'sponsored' } });

代替トークン(例:USDC):

const tokens = await account.paymaster.getSupportedTokens();
const feeDetails = { feeMode: { mode: 'default', gasToken: USDC_ADDRESS } };
const estimate = await account.estimatePaymasterTransactionFee(calls, feeDetails);
const tx = await account.executePaymasterTransaction(calls, feeDetails, estimate.suggested_max_fee_in_gas_token);

メッセージ署名(SNIP-12)

const typedData = {
  types: {
    StarknetDomain: [
      { name: 'name', type: 'shortstring' },
      { name: 'version', type: 'shortstring' },
      { name: 'chainId', type: 'shortstring' },
      { name: 'revision', type: 'shortstring' }
    ],
    Message: [{ name: 'content', type: 'shortstring' }]
  },
  primaryType: 'Message',
  domain: { name: 'MyDapp', version: '1', chainId: 'SN_SEPOLIA', revision: '1' },
  message: { content: 'Hello Starknet' }
};

const signature = await account.signMessage(typedData);
const msgHash = await account.hashMessage(typedData);
const isValid = ec.starkCurve.verify(signature, msgHash, publicKey);

CallData と Cairo の型

import { CallData, cairo, CairoCustomEnum, CairoOption, CairoOptionVariant } from 'starknet';

// Compile with ABI
const calldata = new CallData(abi);
const compiled = calldata.compile('transfer', { recipient: '0x...', amount: cairo.uint256(1000n) });

// Cairo type helpers - always use BigInt (n suffix) for token amounts
cairo.uint256(1000n)          // { low, high } - ALWAYS use BigInt for precision
cairo.felt252(1000)           // BigInt
cairo.felt('0x123')           // hex to felt
cairo.bool(true)              // Cairo bool
cairo.byteArray('Hello')      // ByteArray for long strings

// Short strings (<= 31 chars)
import { shortString } from 'starknet';
shortString.encodeShortString('hello')  // felt252
shortString.decodeShortString('0x...')  // 'hello'

// Enums and Options
const myEnum = new CairoCustomEnum({ Variant1: { value: 123 } });
const some = new CairoOption(CairoOptionVariant.Some, value);

重要: トークンの金額と残高には、常に BigInt(例:1000n)を使用してください。wei の値に Number() や parseFloat() を使用しないでください。JavaScript の数値は 2^53 を超えると精度が失われます。

ERC-20 トークン操作

const erc20 = new Contract(erc20Abi, tokenAddress, account);

// Read balance (returns BigInt - do NOT convert with Number())
const balance = await erc20.balanceOf(account.address);
console.log('Balance (wei):', balance.toString());

// Transfer (use BigInt for amount)
const amount = cairo.uint256(1000000000000000000n); // 1 token (18 decimals)
const tx = await erc20.transfer(recipientAddress, amount);
await provider.waitForTransaction(tx.transaction_hash);

// Approve + transferFrom pattern
await erc20.approve(spenderAddress, cairo.uint256(amount));

ユーティリティ関数

import { stark, ec, encode, num, hash } from 'starknet';

// Key generation
const privateKey = '0x' + encode.buf2hex(ec.starkCurve.utils.randomPrivateKey());
const publicKey = ec.starkCurve.getStarkKey(privateKey);

// Number conversions
num.toHex(123);           // '0x7b'
num.toBigInt('0x7b');     // 123n

// Hashing
hash.getSelectorFromName('transfer');
hash.calculateContractAddressFromHash(salt, classHash, calldata, deployer);

コントラクトのデプロイ

// Deploy via UDC
const { transaction_hash, contract_address } = await account.deploy({
  classHash: '0x...',
  constructorCalldata: CallData.compile({ owner: account.address }),
  salt: stark.randomAddress(), // random felt252 salt (not a private key)
  unique: true
});

// Declare first, then deploy
const declareResponse = await account.declare({
  contract: compiledSierra,
  casm: compiledCasm
});
await provider.waitForTransaction(declareResponse.transaction_hash);

const deployResponse = await account.deploy({
  classHash: declareResponse.class_hash,
  constructorCalldata: CallData.compile({ owner: account.address })
});

// Or combined
const result = await account.declareAndDeploy({
  contract: compiledContract,
  casm: compiledCasm,
  constructorCalldata: CallData.compile({ owner: account.address })
});

Outside Execution(SNIP-9)

別のアカウントに代わってトランザクションを実行します(ガスレス/委任):

const version = await account.getSnip9Version();  // 'V1' | 'V2' | 'UNSUPPORTED'

const outsideTransaction = await account.getOutsideTransaction(
  { caller: executorAddress, execute_after: now, execute_before: now + 3600 },
  calls,
  'V2'
);

// Executor submits the pre-signed transaction
const result = await executorAccount.executeFromOutside(outsideTransaction);

エラー処理

import { LibraryError, RpcError } from 'starknet';

try {
  const tx = await account.execute(calls);
} catch (error) {
  if (error instanceof RpcError) {
    console.error('RPC error:', error.code, error.message);
  } else if (error instanceof LibraryError) {
    console.error('Library error:', error.message);
  }
}

ロギングと設定

import { config, setLogLevel } from 'starknet';

// Global config
config.set('transactionVersion', '0x3');
config.get('transactionVersion');

// Logging
setLogLevel('DEBUG');  // ERROR | WARN | INFO | DEBUG

コメント