@1money/protocol-ts-sdk

中文文档
3.1.1发布于 2026/10/15MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 3157

ts sdk for 1Money protocol network

原始使用文档
文档目录21 节
  1. Quick start
  2. Install
  3. Initialize the API Client
  4. Configure Custom HTTP Headers
  5. Fetch the current checkpoint number
  6. Get checkpoint by number
  7. CDN
  8. Error Handling
  9. API Methods
  10. Chain API
  11. Accounts API
  12. Tokens API
  13. Transactions API
  14. Utility Functions
  15. Calculate Transaction Hash
  16. Derive Token Address
  17. Convert to Hex
  18. Derive a multisig account address
  19. Sign Message (deprecated)
  20. Migrating from 2.x to 3.0
  21. License

@1money/protocol-ts-sdk

1Money Logo

The TS-SDK for 1Money Network Protocol

Build Status NPM downloads npm version install size license

Quick start

Install

npm i -S @1money/protocol-ts-sdk axios viem @ethereumjs/rlp
# or
yarn add @1money/protocol-ts-sdk axios viem @ethereumjs/rlp
# or
pnpm i @1money/protocol-ts-sdk axios viem @ethereumjs/rlp

Initialize the API Client

import { api } from '@1money/protocol-ts-sdk';

// Initialize with default settings (mainnet)
const apiClient = api();

// Or specify testnet network
const testnetClient = api({ network: 'testnet' });

// You can also set a custom timeout (in milliseconds)
const apiClient = api({
  network: 'testnet',
  timeout: 5000 // 5 seconds
});

Configure Custom HTTP Headers

You can set custom HTTP headers that will be included in all API requests:

import { setInitConfig } from '@1money/protocol-ts-sdk/client';

// Set custom headers for all requests
setInitConfig({
  headers: {
    'Authorization': 'Bearer your-token',
    'X-Custom-Header': 'custom-value'
  }
});

// You can also combine with other configuration options
setInitConfig({
  baseURL: 'https://api.custom-domain.com',
  timeout: 10000,
  headers: {
    'Authorization': 'Bearer your-token',
    'X-API-Key': 'your-api-key'
  }
});

Note: Custom headers are automatically merged with axios default headers, ensuring both your custom headers and the library's default security headers are included in all requests.

Fetch the current checkpoint number

const number = await apiClient.checkpoints.getNumber()
  .success(response => {
    console.log('number', response.number);
    return response.number;
  })
  .error(err => {
    console.error('Error:', err);
    // return a default value
    return 0;
  });

// do something with the number
// ...

Get checkpoint by number

const checkpoint = await apiClient.checkpoints.getByNumber(1)
  .success(response => {
    console.log('checkpoint', response);
  });

CDN

<script src="https://unpkg.com/@1money/protocol-ts-sdk@latest/umd/1money-protocol-ts-sdk.min.js"></script>

<script>
  const apiClient = window.$1money.api({
    network: 'testnet'
  });

  async function getNumber () {
    const res = await apiClient.checkpoints.getNumber();
    console.log('res: ', res);
  }

  getNumber();
</script>

Error Handling

Reads return a chainable promise-like wrapper; v2 writes return a plain promise. Every read (apiClient.<module>.get*, apiClient.<module>.estimateFee, etc.) and every legacyV1.* write returns a promise-like object with .success(), .timeout(), .error() and .rest() handlers, shown below. The native v2 write methods (apiClient.tokens.issueToken, apiClient.transactions.payment, and the rest of the TransactionBuilder → authorize → submit pipeline) are plain async functions and return a native Promise — await them in a try/catch instead of chaining .success()/.error(); see the write examples under API Methods below.

v2 write outcomes

A v2 write resolves with { hash } when the node admits the transaction. Otherwise it throws one of these errors. Branch on instanceof:

ThrownsubmittedWhat to do
TransactionSubmissionErrorfalseThe node rejected the request before admission. Fix the cause (err.action, err.status, err.errorCode), then send again.
TransactionLockedErrortrueThe node holds this exact transaction. Resend the same authorized until its receipt appears.
TransactionOutcomeUnknownError'unknown'The SDK cannot tell. Look up err.transactionHash and resend the same authorized until you know the state.
TransactionHashMismatchErrortrueThe node admitted the transaction, but returned a different hash. Do not send again.

Never sign a new transaction for the same intent, at any nonce, after a locked or unknown outcome: the payment can execute twice. Resending the same signed transaction is always safe: it has the same hash, so at most one copy executes. apiClient.transactions.settle(authorized) resends the same signed bytes and resolves with the receipt; send a new write with apiClient.transactions.submitAndSettle(authorized) to do both in one call (see src/api/transactions/README.md). The SDK decides by the action member of the node's error body; on a node without it, a 422 business_transaction_failed and a 408 are unknown, except a few fixed texts of a released node: "already inflight" means locked, and a refusal of the signed bytes (for example a bad signature or a wrong chain id) means rejected. See skills/1money-protocol-sdk/references/client-and-errors.md for the full rules.

Problem details

A node with the API error contract sends an RFC 9457 problem body on its API errors (not on /api/health, /api/status or /metrics). The SDK parses it into err.problem (type ApiProblem) on every ParsedError (the value that .error() gets and that a rejected promise throws), and on the TransactionSubmissionError, TransactionLockedError and TransactionOutcomeUnknownError of a single submit. err.problem is undefined when the body has no string type, for example on an older node; the write errors keep err.action and err.problemType for that case, and the errors that settle throws at the end have no body. An HTTP 401 is not a ParsedError: the default client's login rule passes the raw body to .login(), or resolves the promise with it. That body has type and action as plain members, but no problem.

MemberMeaning
typeThe problem type URI, https://developer.1moneynetwork.com/problem/<identifier>, or about:blank. Compare the whole string.
actionfix_request, retry_later, in_progress, check_status or use_other_node. undefined when the body has no action or one this SDK does not know: on a submission, treat that as check_status.
instanceThe request ID, urn:uuid:<id>. Quote it when you report a problem to a node operator.
title, statusThe type's short summary and the HTTP status.
membersThe typed members of the type, as sent (for example tx_hash, nonce, path). A member can be missing from an older node. txHash and retryAfterSecs read the two common ones.

Decide on type and action, not on message or title: their text can change. A 404 is not always "absent": a path that the node does not serve gives route_not_found.

import type { ParsedError } from '@1money/protocol-ts-sdk';

const TRANSACTION_NOT_FOUND =
  'https://developer.1moneynetwork.com/problem/transaction_not_found';

try {
  await apiClient.transactions.getByHash(hash);
} catch (err) {
  const { problem } = err as ParsedError;
  if (problem?.type === TRANSACTION_NOT_FOUND) {
    // Not found yet. Look it up again later.
  } else {
    console.error(problem?.instance, err);
  }
}

For the chainable wrapper, always implement both handlers for proper error management:

  1. .success(): Handles successful API responses
  2. .timeout(): Specifically handles timeout errors
  3. .error(): Handles all other types of errors
  4. .rest(): A final handler that runs after any of the above handlers complete
import { api } from '@1money/protocol-ts-sdk';

const apiClient = api();

apiClient.someMethod()
  .success(response => {
    // Handle successful response
  })
  .timeout(err => {
    // Handle timeout case
  })
  .error(err => {
    // Handle other errors
  });

You can use rest to handle all other errors:

apiClient.someMethod()
  .success(response => {
    // Handle successful response
  })
  .rest(err => {
    // Handle other cases
  });

Async/Await

You also can use async/await to handle the response:

import { api } from '@1money/protocol-ts-sdk';

const apiClient = api();

try {
  const response = await apiClient.someMethod();
  console.log('Response:', response);
} catch (err) {
  console.error('Error:', err);
}

Promise

You also can use standard promise to handle the response:

import { api } from '@1money/protocol-ts-sdk';

const apiClient = api();

apiClient.someMethod()
  .then(response => {
    console.log('Response:', response);
  })
  .catch(err => {
    console.error('Error:', err);
  });

API Methods

Every write below follows the same native v2 shape: prepare → sign the digest → authorize → submit the resulting AuthorizedTxV2. See Migrating from 2.x to 3.0 and skills/1money-protocol-sdk/references/transactions.md for the full pipeline, every builder's fields, and the legacy LegacyV1TransactionBuilder / api().<module>.legacyV1.* path.

Chain API

Get Chain ID

apiClient.chain.getChainId()
  .success(response => {
    console.log('Current chain id:', response.chain_id);
  })
  .error(err => {
    console.error('Error:', err);
  });

Accounts API

Get Account Nonce

const address = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';
apiClient.accounts.getNonce(address)
  .success(response => {
    console.log('Account nonce:', response);
  })
  .error(err => {
    console.error('Error:', err);
  });

Get Associated Token Account

const address = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';
const token = '0x2cd8999Be299373D7881f4aDD11510030ad1412F';
apiClient.accounts.getTokenAccount(address, token)
  .success(response => {
    console.log('Associated token account:', response);
  })
  .error(err => {
    console.error('Error:', err);
  });

Create a Multisig Account

createMultisig is v2-only — there is no legacyV1 form and no accounts.legacyV1 namespace.

import {
  TransactionBuilder,
  createPrivateKeySigner,
  deriveMultisigAddress
} from '@1money/protocol-ts-sdk';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const creatorAddress = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

// 33-byte SEC1-compressed public keys. 2-20 signers; a weight is a u8
// (1-255) and the threshold must not exceed the total signer weight.
const signers = [
  { public_key: '0x02...', weight: 1 },
  { public_key: '0x03...', weight: 1 }
];
const threshold = 2;

// The node never echoes the new account's address, so compute it first --
// this is the same value the node derives at execution.
const multisigAddress = deriveMultisigAddress(signers, threshold);

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(creatorAddress);

const prepared = TransactionBuilder.createMultisig({
  chain_id,
  nonce,
  signers,
  threshold
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.accounts.createMultisig(authorized);
  console.log('Multisig', multisigAddress, 'created in tx', response.hash);
} catch (err) {
  console.error('Error:', err);
}

See Derive a multisig account address for the derivation on its own.

Tokens API

Get Token Metadata

const tokenAddress = '0x2cd8999Be299373D7881f4aDD11510030ad1412F';
apiClient.tokens.getTokenMetadata(tokenAddress)
  .success(response => {
    console.log('Token metadata:', response);
  })
  .error(err => {
    console.error('Error:', err);
  });

Issue New Token

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const masterAuthority = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(masterAuthority);

// Prepare (validates fields, computes the digest to sign).
const prepared = TransactionBuilder.tokenIssue({
  chain_id,
  nonce,
  symbol: 'MTK',
  name: 'My Token',
  decimals: 18,
  master_authority: masterAuthority,
  is_private: true,
  clawback_enabled: true
});

// Sign the digest, then authorize it into a plain-JSON request.
const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: submitAuthorized returns a plain Promise, not the chainable
// wrapper -- await it in a try/catch.
try {
  const response = await apiClient.tokens.issueToken(authorized);
  console.log('Token issued:', response.token, 'in tx', response.hash);
} catch (err) {
  console.error('Error:', err);
}

Mint Tokens

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const mintAuthority = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(mintAuthority);

const prepared = TransactionBuilder.tokenMint({
  chain_id,
  nonce,
  recipient: '0xa128999be299373d7881f4add11510030ad13512',
  value: '1000000000000000000',
  token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F'
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.tokens.mintToken(authorized);
  console.log('Mint transaction hash:', response.hash);
} catch (err) {
  console.error('Error:', err);
}

The signer must be the token's master_authority or hold a granted mint authority — see Grant Token Authority. A granted authority mints against a finite allowance, which each mint decrements; the master authority is not metered.

Manage Token Blacklist/Whitelist

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';
import { ManageListAction } from '@1money/protocol-ts-sdk/api';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const operatorAddress = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(operatorAddress);

// tokenBlacklist and tokenWhitelist are distinct v2 operations (distinct
// signing hashes) — build the one you intend to submit, they are not
// interchangeable the way the pre-3.0 tokenManageList builder was.
const prepared = TransactionBuilder.tokenBlacklist({
  chain_id,
  nonce,
  action: ManageListAction.Add,
  address: operatorAddress,
  token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F'
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.tokens.manageBlacklist(authorized);
  console.log('Blacklist update transaction hash:', response.hash);
} catch (err) {
  console.error('Error:', err);
}

The whitelist is the same shape end to end — swap both the builder and the submit method, and sign the one you actually intend to submit:

const prepared = TransactionBuilder.tokenWhitelist({
  chain_id,
  nonce,
  action: ManageListAction.Add,
  address: '0xa128999be299373d7881f4add11510030ad13512',
  token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F'
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const response = await apiClient.tokens.manageWhitelist(prepared.authorize(signature));

Whitelists apply to tokens issued with is_private: true. Use ManageListAction.Remove to take an address off either list.

Burn Tokens

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const ownerAddress = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(ownerAddress);

const prepared = TransactionBuilder.tokenBurn({
  chain_id,
  nonce,
  value: '1000000000000000000',
  token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F'
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.tokens.burnToken(authorized);
  console.log('Burn transaction hash:', response.hash);
} catch (err) {
  console.error('Error:', err);
}

Claw Back Tokens

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const clawbackAuthority = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(clawbackAuthority);

const prepared = TransactionBuilder.tokenClawback({
  chain_id,
  nonce,
  token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F',
  from: '0xa128999be299373d7881f4add11510030ad13512',
  recipient: clawbackAuthority,
  value: '1000000000000000000'
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.tokens.clawbackToken(authorized);
  console.log('Clawback transaction hash:', response.hash);
} catch (err) {
  console.error('Error:', err);
}

Clawback moves tokens out of from without that account's signature, so it only works on a token issued with clawback_enabled: true (the default), and the signer must be the token's master_authority or hold a granted AuthorityType.Clawback authority.

Grant Token Authority

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';
import { AuthorityAction, AuthorityType } from '@1money/protocol-ts-sdk/api';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const masterAddress = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(masterAddress);

const prepared = TransactionBuilder.tokenAuthority({
  chain_id,
  nonce,
  action: AuthorityAction.Grant,
  authority_type: AuthorityType.MasterMint,
  authority_address: '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3',
  token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F',
  value: '1000000000000000000000'
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.tokens.grantAuthority(authorized);
  console.log('Authority update transaction hash:', response.hash);
} catch (err) {
  console.error('Error:', err);
}

Pause / Unpause a Token

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';
import { PauseAction } from '@1money/protocol-ts-sdk/api';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const pauseAuthority = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(pauseAuthority);

const prepared = TransactionBuilder.tokenPause({
  chain_id,
  nonce,
  action: PauseAction.Pause, // PauseAction.Unpause to lift it
  token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F'
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.tokens.pauseToken(authorized);
  console.log('Pause transaction hash:', response.hash);
} catch (err) {
  console.error('Error:', err);
}

Update Token Metadata

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const metadataAuthority = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(metadataAuthority);

const prepared = TransactionBuilder.tokenMetadata({
  chain_id,
  nonce,
  name: 'My Token',
  uri: 'https://example.com/my-token.json',
  token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F',
  additional_metadata: [{ key: 'category', value: 'stablecoin' }]
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.tokens.updateMetadata(authorized);
  console.log('Metadata transaction hash:', response.hash);
} catch (err) {
  console.error('Error:', err);
}

additional_metadata replaces the whole list rather than merging into it — send every pair you want to keep. Pass [] to clear it.

Bridge and Mint

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const bridgeOperatorAddress = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(bridgeOperatorAddress);

const prepared = TransactionBuilder.tokenBridgeAndMint({
  chain_id,
  nonce,
  recipient: '0x6324dAc598f9B637824978eD6b268C896E0c40E0',
  value: '25000000000000000000',
  token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F',
  source_chain_id: 1,
  source_tx_hash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
  bridge_metadata: 'bridge_from_chain_1'
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.tokens.bridgeAndMint(authorized);
  console.log('Bridge and mint transaction hash:', response.hash);
} catch (err) {
  console.error('Error:', err);
}

Burn and Bridge

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const senderAddress = '0x6324dAc598f9B637824978eD6b268C896E0c40E0';

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(senderAddress);

const prepared = TransactionBuilder.tokenBurnAndBridge({
  chain_id,
  nonce,
  sender: senderAddress,
  value: '20000000000000000000',
  token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F',
  destination_chain_id: 1,
  destination_address: '0x1234567890abcdef1234567890abcdef12345678',
  escrow_fee: '1000000000000000000',
  bridge_metadata: 'bridge_to_chain_1',
  bridge_param: '0x'
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.tokens.burnAndBridge(authorized);
  console.log('Burn and bridge transaction hash:', response.hash);
} catch (err) {
  console.error('Error:', err);
}

Transactions API

Get Transaction Details

const txHash = '0xf55f9525be94633b56f954d3252d52b8ef42f5fd5f9491b243708471c15cc40c';
apiClient.transactions.getByHash(txHash)
  .success(response => {
    console.log('Transaction details:', response);
  })
  .error(err => {
    console.error('Error:', err);
  });

Get Transaction Receipt

const txHash = '0xf55f9525be94633b56f954d3252d52b8ef42f5fd5f9491b243708471c15cc40c';
apiClient.transactions.getReceiptByHash(txHash)
  .success(response => {
    console.log('Transaction receipt:', response);
  })
  .error(err => {
    console.error('Error:', err);
  });

Estimate Transaction Fee

const fromAddress = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';
const toAddress = '0x6324dAc598f9B637824978eD6b268C896E0c40E0';
const value = '1000000000';
const tokenAddress = '0x2cd8999Be299373D7881f4aDD11510030ad1412F';

apiClient.transactions.estimateFee(fromAddress, toAddress, value, tokenAddress)
  .success(response => {
    console.log('Estimated fee:', response);
  })
  .error(err => {
    console.error('Error:', err);
  });

Submit Payment Transaction

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const senderAddress = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

// Get chain id and current nonce
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(senderAddress);

// Prepare (validates fields, computes the digest to sign). `memo` is always
// sent on the wire; omit the option for the all-empty "no business memo".
const prepared = TransactionBuilder.payment({
  chain_id,
  nonce,
  recipient: '0xa128999be299373d7881f4add11510030ad13512',
  value: '1000000000',
  token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F'
});

// Sign the digest, then authorize it into a plain-JSON request.
const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature); // signature.v must be 0 or 1

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.transactions.payment(authorized);
  console.log('Payment transaction hash:', response.hash);
} catch (err) {
  console.error('Error:', err);
}

Attach a Memo to a Payment

Every v2 builder takes an optional second argument holding the memo. The memo is part of the signed payload, so the same payment with a different memo produces a different signingHash and a different transaction hash — it is not a header or an annotation bolted on afterwards.

import {
  TransactionBuilder,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';

const prepared = TransactionBuilder.payment(
  {
    chain_id,
    nonce,
    recipient: '0xa128999be299373d7881f4add11510030ad13512',
    value: '1000000000',
    token: '0x2cd8999Be299373D7881f4aDD11510030ad1412F'
  },
  {
    memo: {
      type: 'purpose/SALA',
      format: 'text/plain',
      data: 'August salary'
    }
  }
);

// Everything downstream is unchanged -- the memo is already inside the digest.
const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const response = await apiClient.transactions.payment(prepared.authorize(signature));

Field limits, enforced identically by the SDK and the node:

FieldMaxAllowed characters
type128 bytesURL-safe only (RFC 3986 unreserved + gen-delims + sub-delims + %)
format64 bytesURL-safe only, same set
data256 bytesany UTF-8 except NUL, C0/C1 control codepoints, and surrogates

The limits are UTF-8 byte lengths, not character counts — '结算备注' is four characters but twelve bytes. Each field is individually optional; a partial memo fills the rest with empty strings, and omitting the option entirely sends { type: '', format: '', data: '' }, which is still signed.

Validation is local and happens before any signer is invoked: the builder calls validateMemo while computing the digest, so a malformed memo throws rather than spending a signing operation — which matters when the signer is backed by an HSM or KMS.

import { MemoValidationError } from '@1money/protocol-ts-sdk';

try {
  TransactionBuilder.payment(payload, {
    memo: { type: 'a'.repeat(200), format: '', data: '' }
  });
} catch (err) {
  if (err instanceof MemoValidationError) {
    // 'MEMO_TYPE_TOO_LONG' | 'MEMO_TYPE_INVALID_CHARS' | 'MEMO_FORMAT_TOO_LONG'
    // | 'MEMO_FORMAT_INVALID_CHARS' | 'MEMO_DATA_TOO_LONG'
    // | 'MEMO_DATA_CONTROL_CHARS' | 'MEMO_TOO_LARGE'
    console.error(err.code, err.message);
  }
}

Read it back from the transaction (not the receipt), where it is returned for every memo-bearing v2 envelope:

const tx = await apiClient.transactions.getByHash(
  prepared.authorize(signature).transactionHash
);
console.log(tx.memo); // { type: 'purpose/SALA', format: 'text/plain', data: 'August salary' }

The same option works on every v2 builder — tokenIssue, tokenMint, tokenBlacklist, batchPayment and the rest all accept { memo } as their second argument in exactly this shape.

Submit Batch Payment

Pay many recipients of one token in one canonical native-v2 transaction. batchPayment is v2-only: the SDK deliberately exposes no legacy Batch Payment builder or transactions.legacyV1.batchPayment, even if a node still retains a deprecated v1 route.

import {
  TransactionBuilder,
  calculateBatchPaymentOperationsHash,
  createPrivateKeySigner
} from '@1money/protocol-ts-sdk';

// Your private key (DO NOT share or commit your private key)
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
const senderAddress = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';

const token = '0x2cd8999Be299373D7881f4aDD11510030ad1412F';
const operations = [
  { recipient: '0xa128999be299373d7881f4add11510030ad13512', amount: '1000000000' },
  { recipient: '0x6324dAc598f9B637824978eD6b268C896E0c40E0', amount: '2000000000' }
];

// An unsigned, non-binding point-in-time quote. It is not a fee cap and is
// not copied into the payload, signing hash, authorization, or submission.
const quote = await apiClient.transactions.estimateBatchPaymentFee({
  from: senderAddress,
  token,
  operations
});
console.log('Estimated fee:', quote.fee, 'plan:', quote.plan);

// Get chain id and a fresh current nonce.
const { chain_id } = await apiClient.chain.getChainId();
const { nonce } = await apiClient.accounts.getNonce(senderAddress);

// This optional field is a caller choice. If supplied, the builder recomputes
// it and rejects a mismatch locally before any signer is invoked.
const operationsHash = calculateBatchPaymentOperationsHash(operations);

const prepared = TransactionBuilder.batchPayment({
  chain_id,
  nonce,
  token,
  operations,
  created_at: Math.floor(Date.now() / 1000), // unix seconds
  operations_hash: operationsHash,
  batch_id: 'payroll-2026-08'
}, {
  memo: {
    type: 'purpose/SALA',
    format: 'text/plain',
    data: 'payroll-2026-08'
  }
});

const signature = await createPrivateKeySigner(privateKey).signDigest(prepared.signingHash);
const authorized = prepared.authorize(signature);

// v2 write: returns a plain Promise -- await it in a try/catch.
try {
  const response = await apiClient.transactions.batchPayment(authorized);
  // The SDK already checks this case-insensitively before resolving.
  if (
    response.hash.toLowerCase() !==
    authorized.transactionHash.toLowerCase()
  ) {
    throw new Error('SDK already rejects a server/local hash mismatch');
  }
  console.log('Batch payment transaction hash:', response.hash);
} catch (err) {
  console.error('Error:', err);
}

operations must not be empty; each recipient and amount must be nonzero, and the aggregate cannot exceed U256::MAX. operations_hash is optional, but a provided value must be the canonical calculateBatchPaymentOperationsHash(operations) result or preparation fails locally. batch_id is signed correlation metadata only: it has no uniqueness, deduplication, idempotency, or replay guarantee. The transaction nonce remains the transaction-level replay mechanism. Each operation is signed and sent as exactly { recipient, amount }; additional runtime properties from untyped callers are ignored and do not reach the authorized request.

Batch Payment always signs and transmits a complete memo. Omitting memo is valid and sends { type: '', format: '', data: '' }; those three empty strings are still part of the signed payload. The obsolete client-side max_fee field is not accepted. The actual sender debit is the sum of all operation amounts plus the receipt's actual string fee_used, not the estimate. Batch enablement, operation-count and encoded-size limits, and the fee asset are dynamic node governance rules, so a successful estimate is not an admission guarantee.

After the returned hash is indexed, read its receipt (or finalized receipt) and use execution_events to inspect the recipients:

const receipt = await apiClient.transactions.getReceiptByHash(
  authorized.transactionHash
);

// Batch Payment has no single recipient. This zero address is a sentinel.
console.log(receipt.success_info?.receiver);

for (const event of receipt.execution_events ?? []) {
  if (event.event_type === 'PaymentExecuted') {
    console.log(event.recipient, event.amount);
  }
}

console.log('actual fee:', receipt.fee_used);

For a Batch Payment, success_info.receiver is the zero-address sentinel, not a transfer destination; real recipients and amounts are the ordered PaymentExecuted events. batch_info.failure is reserved for forward compatibility but is null in current production-shaped responses and is not a terminal-failure signal. If any operation fails, the node rolls back every recipient credit, the sender debit, and operator-fee movement together; do not infer a non-null failure object from that rule.

To verify an intentional rollback scenario, do not rely on a receipt or an error body as a terminal signal: failure can be receipt-less or ambiguous, and batch_info.failure remains null in current behavior. Before submission, snapshot balances for the sender, every planned recipient, and the operator fee recipient. Submit once, make only a bounded observation of the submission and ordinary/finalized receipt paths, then verify every snapshot is unchanged. Use an isolated sender so any uncertain nonce or mempool state cannot affect other transactions.

Utility Functions

Calculate Transaction Hash

import { calcTxHash } from '@1money/protocol-ts-sdk';

// Create the payload array
const payload = [
  1212101, // chain_id
  2, // nonce
  '0x0000000000000000000000000000000000000000', // recipient
  1024, // value
  '0x0000000000000000000000000000000000000000', // token
];

// Create the signature object
const signature = {
  r: '0xe9ef6ce7aaeb4656f197b63a96c932ab5e0fd2df0913f6af1c8e7b1879e5ed0a',
  s: '0x68a9cbaa35af5e3d896a2841d19a42dba729380a1c91864403de872578f6f6c3',
  v: 0,
};

// Calculate the transaction hash
const hash = calcTxHash(payload, signature);
console.log('Transaction hash:', hash);

Derive Token Address

import { deriveTokenAddress } from '@1money/protocol-ts-sdk';

const walletAddress = '0xA634dfba8c7550550817898bC4820cD10888Aac5';
const mintAddress = '0x8E9d1b45293e30EF38564582979195DD16A16E13';

// Derive the token account address
const tokenAddress = deriveTokenAddress(walletAddress, mintAddress);
console.log('Token account address:', tokenAddress);

Convert to Hex

import { toHex } from '@1money/protocol-ts-sdk';

// Convert different types to hex
const boolHex = toHex(true); // '0x1' (minimal hex, not zero-padded)
const numHex = toHex(123); // '0x7b'
const strHex = toHex('hello'); // '0x68656c6c6f'
const arrHex = toHex([1, 2, 3]); // '0x010203'

Derive a multisig account address

Compute the address a native multisig account will get before creating it — accounts.createMultisig only returns { hash }, never the address:

import { deriveMultisigAddress } from '@1money/protocol-ts-sdk';

const address = deriveMultisigAddress(
  [
    { public_key: '0x02...', weight: 1 },
    { public_key: '0x03...', weight: 1 },
  ],
  2 // threshold
); // → '0x…'

Sign Message (deprecated)

import { signMessage } from '@1money/protocol-ts-sdk';

// Your private key (DO NOT share or commit your private key)
const privateKey = 'YOUR_PRIVATE_KEY';

// Create the payload array for signing
const payload = [
  1212101, // chain_id
  2, // nonce
  '0x0000000000000000000000000000000000000000', // recipient
  '1000000000000000000', // value (in wei)
  '0x0000000000000000000000000000000000000000', // token
];

// Sign the message
const signature = await signMessage(payload, privateKey);

signMessage and encodePayload are legacy @deprecated helpers. Prefer TransactionBuilder (or LegacyV1TransactionBuilder during a migration window) for new code — see Migrating from 2.x to 3.0.

Migrating from 2.x to 3.0

3.0 makes the domain-separated native v2 signing scheme the default and moves the pre-3.0 scheme behind an explicit namespace. This is a breaking change for any code calling TransactionBuilder directly.

  • TransactionBuilder now means v2. TransactionBuilder.payment(...) (and every other operation) returns a PreparedTxV2, not the old PreparedTx. It no longer has .sign() / .attachSignature() / .rlpBytes / .signatureHash; it has .signingHash and .authorize(signature) instead.

  • The pre-3.0 scheme still exists, unchanged, as LegacyV1TransactionBuilder (same builder names, same .sign()/ .attachSignature()/.toRequest() shape as before) paired with api().<module>.legacyV1.* submit methods (e.g. client.transactions.legacyV1.payment(...), client.tokens.legacyV1.manageBlacklist(...)). accounts has no legacyV1 namespace, and batchPayment / createMultisig have no legacy form at all — both are v2-only.

  • Write methods now take an AuthorizedTxV2, the output of prepared.authorize(signature), instead of a request object assembled from signed.toRequest(). AuthorizedTxV2 is plain JSON and can cross a process boundary (sign in one place, submit from another). Each call to authorize returns an independent request snapshot; mutating an earlier request, including its memo, cannot affect a later authorization or its hashes.

  • Write methods now return a native Promise, not the chainable wrapper. This is the easy one to miss, because reads are unchanged — the same client now has two call styles:

    // Read: still the promise wrapper.
    apiClient.tokens.getTokenMetadata(token).success(res => …).error(err => …);
    
    // Write: a plain Promise. Calling .success() on it throws
    // "apiClient.tokens.issueToken(...).success is not a function".
    try {
      const res = await apiClient.tokens.issueToken(authorized);
    } catch (err) { … }
    

    So .success()/.error()/.timeout()/.rest() chains on any 2.x write call must become await in a try/catch. TypeScript flags these at compile time; plain-JavaScript callers only find out at runtime.

  • tokenManageList is gone. It split into tokenBlacklist and tokenWhitelist — distinct operations with distinct signing hashes, paired with tokens.manageBlacklist / tokens.manageWhitelist respectively. You can no longer build one payload and submit it to either endpoint.

  • memo is now always sent on the v2 surface. Every operation carries a memo on the wire; omitting the memo option sends the all-empty { type: '', format: '', data: '' } rather than leaving the field off entirely. This differs from the pre-3.0 behavior, where an absent memo took a different code path from a present one.

  • Batch Payment uses the canonical v2 payload. The removed max_fee field is neither signed nor accepted; request an unsigned transactions.estimateBatchPaymentFee({ from, token, operations }) quote instead. The shared EstimateFee response now has fee: string and optional plan?: string, so this also applies to the existing estimateFee() call.

  • Receipt fields are corrected for all callers. TransactionReceipt.fee_used is a decimal string (not a number), and to is renamed to recipient. FinalizedTransactionReceipt extends this corrected common receipt shape, so finalized-read callers must make the same updates.

  • Signature v must be 0 or 1 on the v2 surface; a legacy 27/28 is rejected by authorize, not converted.

There is no published sunset date for the legacy /v1 write surface or for LegacyV1TransactionBuilder — the node does not advertise one, and operators run the cutover on their own schedule. Check client.status.getNativeWriteStatus() to see which surface is currently live before depending on either one.

See skills/1money-protocol-sdk/references/transactions.md for the full v2 pipeline and every builder's fields, and skills/1money-protocol-sdk/references/client-and-errors.md for the new v2 error codes and TransactionHashMismatchError.

License

MIT

版本与发布记录

@1money/protocol-ts-sdk 共发布 27 个版本,首次发布于 2025/08/18;过去 12 个月发布了 24 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
3.1.1latest2026/10/1523 天前
3.1.02026/10/1424 天前
3.0.02026/08/142 个月前
2.1.12026/06/264 个月前
2.1.02026/06/224 个月前
2.1.0-beta.1beta2026/06/224 个月前

其他发布通道:alpha → 1.1.2-alpha.7,beta → 2.1.0-beta.1。

关于 @1money/protocol-ts-sdk 的常见问题

@1money/protocol-ts-sdk 是什么?

ts sdk for 1Money protocol network

如何安装 @1money/protocol-ts-sdk?

在项目目录运行 npm install @1money/protocol-ts-sdk,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:viem(>=2.0.0 <3.0.0)、axios(>=1.15.0 <2.0.0)、@ethereumjs/rlp(>=10.0.0 <11.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@1money/protocol-ts-sdk 支持 TypeScript 吗?

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

@1money/protocol-ts-sdk 支持 ESM 还是 CommonJS?

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

@1money/protocol-ts-sdk 可以商用吗?

@1money/protocol-ts-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@1money/protocol-ts-sdk 的下载量有多少?

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

@1money/protocol-ts-sdk 还在维护吗?

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