Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 91 additions & 62 deletions modules/express/src/typedRoutes/api/v2/sendCoins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { SendManyResponse, EIP1559Params, MemoParams, TokenEnablement } from './
* Request parameters for sending to a single recipient (v2)
*/
export const SendCoinsRequestParams = {
/** The coin identifier (e.g., 'btc', 'tbtc', 'eth', 'teth') */
/** A cryptocurrency or token ticker symbol. (e.g., 'btc', 'tbtc', 'eth', 'teth') */
coin: t.string,
/** The ID of the wallet */
/** The wallet ID. */
id: t.string,
} as const;

Expand All @@ -24,100 +24,140 @@ export const SendCoinsRequestParams = {
* and calls wallet.sendMany(), so the response structure is identical.
*/
export const SendCoinsRequestBody = {
/** The destination address - REQUIRED */
/** Destination address (length ≤ 500) */
address: t.string,

/** The amount to send in base units (e.g., satoshis for BTC, wei for ETH) - REQUIRED */
/** Amount in base units (e.g. satoshi, wei, drops, stroops). For doge, only string is allowed. */
amount: t.union([t.number, t.string]),

/** Data field for the transaction (e.g., for Ethereum contract calls) */
/** (ETH only) Optional data to pass to the transaction */
data: optional(t.string),

/** Fee limit for this transaction (e.g., for Tron TRC20 tokens) */
feeLimit: optional(t.string),

/** The wallet passphrase to decrypt the user key */
/** Passphrase to decrypt the user key on the wallet */
walletPassphrase: optional(t.string),

/** The extended private key (alternative to walletPassphrase) */
xprv: optional(t.string),

/** The private key (prv) in string form */
/** Optional, private key in string form, if `walletPassphrase` is not available or encrypted private key is not stored by BitGo. */
prv: optional(t.string),

/** Message to attach to the transaction */
message: optional(t.string),

/** Minimum number of confirmations needed for an unspent to be included (defaults to 1) */
/** The unspent selection for the transaction will only consider unspents with at least this many confirmations to be used as inputs. Does not apply to change outputs unless used in combination with enforceMinConfirmsForChange. */
minConfirms: optional(t.number),

/** If true, minConfirms also applies to change outputs */
/** When set to true, will enforce minConfirms for change outputs. Defaults to false. */
enforceMinConfirmsForChange: optional(t.boolean),

/** Custodian transaction ID (for institutional custody integrations like Metamask) */
custodianTransactionId: optional(t.string),

/** Token name for token transfers */
/** Token name, defined in the BitGoJS Statics package. */
tokenName: optional(t.string),

// All SendManyOptions fields are also supported via the SendOptions index signature
// Including these commonly used fields explicitly for better documentation:

/** Estimate fees to aim for first confirmation within this number of blocks */
/**
* (BTC only) The number of blocks required to confirm a transaction. You can use `numBlocks` to estimate the fee rate
* by targeting confirmation within a given number of blocks. If both `feeRate` and `numBlocks` are absent, the
* transaction defaults to 2 blocks for confirmation.
*
* Note: The `maxFeeRate` limits the fee rate generated by `numBlocks`.
*/
numBlocks: optional(t.number),

/** The desired fee rate for the transaction in base units per kilobyte (e.g., satoshis/kB) */
/**
* Custom fee rate (in base units) per kilobyte (or virtual kilobyte). For example, satoshis per kvByte.
*
* If the `feeRate` is less than the minimum required network fee, then the minimum fee applies. For example,
* 1000 sat/kvByte, a flat 1000 microAlgos, or a flat 10 drops of xrp. For XRP, the actual fee is usually
* 4.5 times the open ledger fee.
*
* Note: The `feeRate` overrides the `maxFeeRate` and `minFeeRate`.
*/
feeRate: optional(t.union([t.number, t.string])),

/** Fee multiplier (multiplies the estimated fee by this factor) */
/**
* (UTXO only) Custom multiplier to the `feeRate`. The resulting fee rate is limited by the `maxFeeRate`.
* For replace-by-fee (RBF) transactions (that include `rbfTxIds`), the `feeMultiplier` must be greater than 1,
* since it's an absolute fee multiplier to the transaction being replaced.
*
* Note: The `maxFeeRate` limits the fee rate generated by `feeMultiplier`.
*/
feeMultiplier: optional(t.number),

/** The maximum limit for a fee rate in base units per kilobyte */
/**
* (BTC only) The maximum fee rate (in base units) per kilobyte (or virtual kilobyte). For example, satoshis per kvByte.
* The `maxFeeRate` limits the fee rate generated by both `feeMultiplier` and `numBlocks`.
*
* Note: The `feeRate` overrides the `maxFeeRate`.
*/
maxFeeRate: optional(t.number),

/** Target number of unspents to maintain in the wallet */
/**
* Defaults to 1000. Specifies the minimum count of good-sized unspents to maintain in the wallet.
* Change splitting ceases when the wallet has `targetWalletUnspents` good-sized unspents.
*
* Note: Wallets that continuously send a high count of transactions will automatically split large change amounts
* into multiple good-sized change outputs while they have fewer than `targetWalletUnspents` good-sized unspents
* in their unspent pool. Breaking up large unspents helps to reduce the amount of unconfirmed funds in flight in
* future transactions, and helps to avoid long chains of unconfirmed transactions. This is especially useful for
* newly funded wallets or recently refilled send-only wallets.
*/
targetWalletUnspents: optional(t.number),

/** Minimum value of unspents to use (in base units) */
/** Ignore unspents smaller than this amount of base units (e.g. satoshis). For doge, only string is allowed. */
minValue: optional(t.union([t.number, t.string])),

/** Maximum value of unspents to use (in base units) */
/** Ignore unspents larger than this amount of base units (e.g. satoshis). For doge, only string is allowed. */
maxValue: optional(t.union([t.number, t.string])),

/** Custom sequence ID for the transaction */
/**
* A `sequenceId` is a unique and arbitrary wallet identifier applied to transfers and transactions at creation.
* It is optional but highly recommended. With a `sequenceId` you can easily reference transfers and transactions—
* for example, to safely retry sending. Because the system only confirms one send request per `sequenceId`
* (and fails all subsequent attempts), you can retry sending without the risk of double spending.
* The `sequenceId` is only visible to users on the wallet and is not shared publicly.
*/
sequenceId: optional(t.string),

/** Absolute max ledger the transaction should be accepted in (for XRP) */
/** (XRP only) Absolute max ledger the transaction should be accepted in, whereafter it will be rejected. */
lastLedgerSequence: optional(t.number),

/** Relative ledger height (in relation to the current ledger) that the transaction should be accepted in */
/** (XRP only) Relative ledger height (in relation to the current ledger) that the transaction should be accepted in, whereafter it will be rejected. */
ledgerSequenceDelta: optional(t.number),

/** Custom gas price to be used for sending the transaction (for account-based coins) */
/** Custom gas price to be used for sending the transaction. Only for ETH and ERC20 tokens. */
gasPrice: optional(t.number),

/** Set to true to disable automatic change splitting for purposes of unspent management */
/** Defaults to false. Set `true` to disable automatic change splitting. Also see: `targetWalletUnspents` */
noSplitChange: optional(t.boolean),

/** Array of specific unspent IDs to use in the transaction */
/** Used to explicitly specify the unspents to be used in the input set in the transaction. Each unspent should be in the form `prevTxId:nOutput`. */
unspents: optional(t.array(t.string)),

/** Comment to attach to the transaction */
/** Optional metadata (only persisted in BitGo) to be applied to the transaction. Use this to add transaction-specific information such as the transaction's purpose or another identifier that you want to reference later. The value is shown in the UI in the transfer listing page. (length ≤ 256) */
comment: optional(t.string),

/** One-time password for 2FA */
otp: optional(t.string),

/** Specifies the destination of the change output */
/** Specifies a custom destination address for the transaction's change output(s) (length ≤ 500) */
changeAddress: optional(t.string),

/** If true, allows using an external change address */
allowExternalChangeAddress: optional(t.boolean),

/** Send this transaction using coin-specific instant sending method (if available) */
/** (DASH only) Specifies whether or not to use Dash's "InstantSend" feature when sending a transaction. */
instant: optional(t.boolean),

/** Memo to use in transaction (supported by Stellar, XRP, etc.) */
/** Extra transaction information for CSPR, EOS, HBAR, RUNE, STX, TON, XLM, and XRP. Required for XLM transactions. Note: For XRP this is the destination tag (DT). For CSPR this is the transfer ID. */
memo: optional(MemoParams),

/** Transfer ID for tracking purposes */
Expand All @@ -126,22 +166,34 @@ export const SendCoinsRequestBody = {
/** EIP-1559 fee parameters for Ethereum transactions */
eip1559: optional(EIP1559Params),

/** Gas limit for the transaction (for account-based coins) */
/** Custom gas limit to be used for sending the transaction. Only for ETH and ERC20 tokens. */
gasLimit: optional(t.number),

/** Type of transaction (e.g., 'trustline' for Stellar) */
/**
* Required for transactions from MPC wallets. "acceleration" speeds up transactions with a certain nonce by
* adjusting the gas setting. "accountSet" is for XRP AccountSet transactions. "consolidate" combines multiple
* UTXO inputs into fewer outputs. "enabletoken" is for SOL. "fanout" splits UTXO inputs into many smaller outputs
* (UTXO coins only). "stakingLock" and "stakingUnlock" are for Stacks delegations. "transfer" is for native-asset
* transfers. "trustline" is for Stellar trustline transactions.
* Possible types include: [acceleration, accountSet, consolidate, enabletoken, fanout, stakingLock, stakingUnlock,
* transfer, transfertoken, trustline].
*
* For AVAX, possible types include: `addValidator`, `export`, and `import`.
* For XRP, possible types include: `payment` and `accountSet`. The default is `payment`.
* For STX, type is required.
*/
type: optional(t.string),

/** If true, enables hop transactions for exchanges */
/** (ETH, AVAXC and POLYGON) Set to true if funds to destination need to come from single sig address. */
hop: optional(t.boolean),

/** Address type for the transaction (e.g., 'p2sh', 'p2wsh') */
/** @deprecated Use `changeAddressType` instead. The type of address to create for change. One of `p2sh`, `p2shP2wsh`, `p2wsh`, or `p2tr`. */
addressType: optional(t.string),

/** Change address type (e.g., 'p2sh', 'p2wsh') */
/** The address type for the change address. One of `p2sh`, `p2shP2wsh`, `p2wsh`, `p2tr` or `p2trMusig2`. */
changeAddressType: optional(t.string),

/** Transaction format (legacy or psbt) */
/** [UTXO only] Format of the returned transaction hex serialization. `legacy` for serialized transaction in custom bitcoinjs-lib format. `psbt` for BIP174 serialized transaction. Defaults to `legacy`. */
txFormat: optional(t.union([t.literal('legacy'), t.literal('psbt'), t.literal('psbt-lite')])),

/** If set to false, sweep all funds including required minimums (e.g., DOT requires 1 DOT minimum) */
Expand All @@ -153,7 +205,7 @@ export const SendCoinsRequestBody = {
/** NFT ID (for NFT transfers) */
nftId: optional(t.string),

/** Transaction nonce (for account-based coins) */
/** (DOT only) A nonce ID is a number used to protect private communications by preventing replay attacks. This is an advanced option where users can manually input a new nonce value in order to correct or fill in a missing nonce ID value. */
nonce: optional(t.string),

/** If true, only preview the transaction without sending */
Expand All @@ -162,7 +214,7 @@ export const SendCoinsRequestBody = {
/** Receive address (for specific coins like ADA) */
receiveAddress: optional(t.string),

/** Messages to be signed with specific addresses */
/** [UTXO only] An array of messages that you sign with the wallet keys using the BIP322 format. If passed, the recipients array must be empty. */
messages: optional(
t.array(
t.type({
Expand All @@ -184,13 +236,13 @@ export const SendCoinsRequestBody = {
/** Non-participation flag (for governance/staking protocols like Algorand) */
nonParticipation: optional(t.boolean),

/** Valid from block height */
/** Optional block this transaction is valid from. */
validFromBlock: optional(t.number),

/** Valid to block height */
/** Optional block this transaction is valid until. */
validToBlock: optional(t.number),

/** Reservation parameters for unspent management */
/** Optional parameter for UTXO coins to automatically reserve the unspents that are used in the build. Useful for Cold wallets. If using, must set `expireTime`. */
reservation: optional(
t.partial({
expireTime: t.string,
Expand Down Expand Up @@ -355,30 +407,7 @@ export const SendCoinsRequestBody = {
} as const;

/**
* Send coins to a single recipient (v2)
*
* This endpoint is a convenience wrapper around the sendMany endpoint that accepts
* a single address and amount instead of a recipients array.
*
* Internally, wallet.send() converts the address and amount into a recipients array
* with a single recipient and calls wallet.sendMany(). This means:
* 1. All sendMany parameters are supported
* 2. The response structure is identical to sendMany
*
* The endpoint:
* 1. Validates the address and amount parameters
* 2. Builds a transaction to the specified address
* 3. Signs the transaction with the user's key (decrypted with walletPassphrase or xprv)
* 4. Requests a signature from BitGo's key
* 5. Sends the fully-signed transaction to the blockchain network
*
* Supports:
* - TSS wallets with txRequest flow
* - Custodial wallets
* - Traditional multisig wallets
* - Account-based and UTXO-based coins
* - Token transfers
* - Advanced features like memo fields, hop transactions, EIP-1559 fees
* This call allows you to create and send cryptocurrency to a destination address.
*
* @operationId express.v2.wallet.sendcoins
* @tag express
Expand Down
Loading