diff --git a/.gitattributes b/.gitattributes index 19f06a0..deaaaeb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,8 @@ /docs export-ignore /examples export-ignore /tests export-ignore +/CONTRIBUTING.md export-ignore +/ROADMAP.md export-ignore /.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c130f5b..e1ad17a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,9 +70,15 @@ jobs: with: php-version: ${{ matrix.php }} extensions: curl - coverage: none + coverage: pcov ini-values: error_reporting=-1, display_errors=On - uses: ramsey/composer-install@v3 with: dependency-versions: ${{ matrix.dependencies }} - - run: vendor/bin/phpunit + - run: vendor/bin/phpunit --coverage-clover=coverage.xml + - name: Upload coverage + if: matrix.php == '8.4' && matrix.dependencies == 'highest' + uses: codecov/codecov-action@v5 + with: + files: coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index c9bec69..ba0890e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ composer.lock /local_tests/* .phpunit.result.cache .php-cs-fixer.cache +coverage.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index d8da1aa..ab4c5f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ Entries for 2.2.3 and earlier were reconstructed from the git history in October ## [Unreleased] +## [2.3.0] - 2026-10-18 + ### Added - `BitGoSDK::toBaseUnits()` and `fromBaseUnits()` convert between decimal amounts and base units exactly, at any number of decimals (for example 18 for ETH). @@ -25,7 +27,7 @@ Entries for 2.2.3 and earlier were reconstructed from the git history in October - Dev tooling: PHPUnit 9.6, PHPStan (level 6, with a baseline for the existing code) and PHP-CS-Fixer (PSR-12). - A GitHub Actions CI workflow: `php -l` and PHPUnit on PHP 7.4 to 8.5 with lowest and highest dependencies, plus PHPStan and a coding-standard check. - Request-shape tests for every public request method, checking the HTTP method, URL, headers and body. Requests go through an internal transport that tests replace with a recorder, so the test suite no longer touches the network. -- Composer scripts: `composer test`, `composer analyse`, `composer cs` and `composer cs:fix`. +- Composer scripts: `composer check` (all checks), `composer test`, `composer analyse`, `composer cs` and `composer cs:fix`. - `homepage` and `support` (issues, source, security policy) in `composer.json`. ### Changed @@ -37,13 +39,13 @@ Entries for 2.2.3 and earlier were reconstructed from the git history in October - Requests no longer force IPv4. cURL uses IPv4 or IPv6 as the system allows. A local address bound with `setRequestIP()` decides the IP version. - `BitGoSDK::setRequestIP()` returns `true` if the IP was applied, and `false` on testnet or for an invalid IP. - **TLS certificates are checked against the system CA store.** The bundled 2022 `cacert.pem` is gone. If PHP finds no system CA store, the Mozilla bundle from the new `composer/ca-bundle` dependency is used. -- `ext-bcmath` is no longer required. -- **The minimum PHP version is now 7.4** (was 7.0). +- `ext-bcmath` is no longer required. `ext-json` is now declared, since the SDK always needed it. +- **The minimum PHP version is now 7.4** (was 7.0). PHP 7.4 and 8.x are supported. - The project is now licensed under MIT. 2.2.3 and earlier were released under GPL-3.0. The file headers say so too. - The README lists the current requirements, links to the docs on developers.bitgo.com, explains how to reach Express over TLS and names the minimum Express version (14.1.3). - `src/` and `examples/` were reformatted to PSR-12. This is whitespace and brace placement only, with no behaviour change. - The examples run without notices on PHP 8.4, from any directory. They pass BTC amounts to `toSatoshi()` as strings and check for errors after `unlockSession()`. -- Composer dist archives no longer include `tests/`, `examples/`, `docs/` or dotfiles. The examples are still available in the repository. +- Composer dist archives no longer include `tests/`, `examples/`, `docs/`, `CONTRIBUTING.md`, `ROADMAP.md` or dotfiles. The examples are still available in the repository. ### Deprecated @@ -229,7 +231,8 @@ These still work and will be removed in 3.0. - First release on Packagist: a BitGo API v1 client for wallets, addresses, labels, keychains and transactions. The `1.0` tag points to the same commit. -[Unreleased]: https://github.com/neto737/BitGoSDK-PHP/compare/2.2.3...HEAD +[Unreleased]: https://github.com/neto737/BitGoSDK-PHP/compare/2.3.0...HEAD +[2.3.0]: https://github.com/neto737/BitGoSDK-PHP/compare/2.2.3...2.3.0 [2.2.3]: https://github.com/neto737/BitGoSDK-PHP/compare/2.2.2...2.2.3 [2.2.2]: https://github.com/neto737/BitGoSDK-PHP/compare/2.2.1...2.2.2 [2.2.1]: https://github.com/neto737/BitGoSDK-PHP/compare/2.2.0...2.2.1 diff --git a/README.md b/README.md index 4276389..644e091 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,210 @@ -# BitGoSDK PHP - -BitGoSDK written in PHP. This SDK contains methods for easily interacting with the BitGo API. - -[![Latest Stable Version](https://poser.pugx.org/neto737/bitgosdk-php/version?style=for-the-badge)](https://packagist.org/packages/neto737/bitgosdk-php) -[![Total Downloads](https://poser.pugx.org/neto737/bitgosdk-php/downloads?style=for-the-badge)](https://packagist.org/packages/neto737/bitgosdk-php) -[![Latest Unstable Version](https://poser.pugx.org/neto737/bitgosdk-php/v/unstable?style=for-the-badge)](//packagist.org/packages/neto737/bitgosdk-php) -[![License](https://poser.pugx.org/neto737/bitgosdk-php/license?style=for-the-badge)](https://packagist.org/packages/neto737/bitgosdk-php) -[![PHP Version Require](https://poser.pugx.org/neto737/bitgosdk-php/require/php?style=for-the-badge)](https://packagist.org/packages/neto737/bitgosdk-php) +

BitGoSDK PHP

+ +

+ A PHP client for the BitGo API and BitGo Express.
+ Manage wallets, addresses, transfers, policies and webhooks, and send coins, from your PHP code. +

+ +

+ Latest Stable Version + Total Downloads + PHP 7.4 or later + CI + Coverage + License +

+ +--- + +## Features + +- **Two clients:** `BitGoSDK` talks to the BitGo API for wallets, addresses, transfers, keychains, policies, sharing, approvals and webhooks. `BitGoExpress` talks to your own [BitGo Express](#bitgo-express) instance for anything that needs your keys, such as creating wallets and sending coins. +- **Any BitGo coin,** mainnet or testnet, with `CurrencyCode` constants for 95 coins and tokens and `AddressType` constants for every UTXO address type. +- **Exact amounts:** pass an `int` or a string of digits, and amounts are always sent as strings. `toBaseUnits()` and `fromBaseUnits()` convert at any number of decimals without floats, so 18-decimal ETH amounts don't overflow. +- **Errors you can check:** a failed call returns `['error' => …, 'status' => …]` instead of throwing, so an `isset($r['error'])` check is all you need. +- **TLS verification on by default,** against the system CA store, with a custom CA option for Express or a TLS-inspecting proxy. +- **Request timeouts,** opt-in IPv4 forcing and binding to a local IP. +- **Plain PHP arrays** as results. The only dependencies are `ext-curl` and `composer/ca-bundle`. +- Tested on PHP 7.4 to 8.5. ## Requirements -- PHP 7.4 or newer, with the cURL extension +- PHP 7.4 or later, with the cURL extension - A BitGo account and an [access token](https://developers.bitgo.com/docs/get-started-access-tokens) -- A [BitGo Express](#bitgo-express) instance, for anything that needs your keys, such as creating wallets or sending +- A [BitGo Express](#bitgo-express) instance, version 14.1.3 or later, for anything that needs your keys ## Installation -Install the SDK with [Composer](https://getcomposer.org/): - ```sh composer require neto737/bitgosdk-php ``` -Or add it to the `require` section of your `composer.json`: +## Quick start -```json -"neto737/bitgosdk-php": "^2.3" -``` +```php +use neto737\BitGoSDK\BitGoExpress; +use neto737\BitGoSDK\BitGoSDK; +use neto737\BitGoSDK\Enum\CurrencyCode; -Then load Composer's autoloader in your application: +// Calls that don't need your keys go straight to BitGo. The third argument selects testnet. +$bitgo = new BitGoSDK('YOUR_ACCESS_TOKEN', CurrencyCode::BITCOIN_TESTNET, true); +$bitgo->walletId = 'YOUR_WALLET_ID'; -```php -require 'vendor/autoload.php'; +$wallet = $bitgo->getWallet(); // ['id' => '...', 'label' => '...', 'balanceString' => '...', ...] +$address = $bitgo->createWalletAddress(); // ['address' => '2N...', 'chain' => 10, ...] -use neto737\BitGoSDK\BitGoSDK; +// Sending needs an unlocked session. The OTP is always 000000 on testnet. +$unlock = $bitgo->unlockSession('000000'); +if (isset($unlock['error'])) { + exit($unlock['error']); +} + +// Anything that signs goes through your BitGo Express instance +$express = new BitGoExpress('localhost', 3080, CurrencyCode::BITCOIN_TESTNET); +$express->accessToken = 'YOUR_ACCESS_TOKEN'; +$express->walletId = 'YOUR_WALLET_ID'; + +$amount = BitGoSDK::toSatoshi('0.001'); // 100000. Pass BTC amounts as strings to avoid float rounding. +$tx = $express->sendTransaction('DESTINATION_ADDRESS', $amount, 'YOUR_WALLET_PASSPHRASE'); ``` -## Example +More examples are in [`examples/`](examples/). -```php -require 'vendor/autoload.php'; +> [!NOTE] +> Requests to Express carry wallet passphrases and private keys. Use plain `http://` only when Express runs on the same host. Otherwise see [Use TLS unless Express is on the same host](#use-tls-unless-express-is-on-the-same-host). -use neto737\BitGoSDK\BitGoSDK; -use neto737\BitGoSDK\Enum\CurrencyCode; +## What's covered + +### `BitGoSDK`: the BitGo API + +| Area | Methods | +| --- | --- | +| User and session | `getCurrentUserProfile()`, `getSessionInfo()`, `lockSession()`, `unlockSession()`, `logout()` | +| Wallets | `listWallets()`, `getWallet()`, `getWalletByAddress()`, `getTotalBalances()`, `getMaximumSpendable()`, `freezeWallet()` | +| Addresses | `createWalletAddress()`, `getWalletAddress()`, `getWalletAddresses()`, `updateWalletAddress()` | +| Transfers and unspents | `listWalletTransfers()`, `getWalletTransfer()`, `getWalletTransferBySequenceId()`, `getWalletTransactions()`, `getWalletTransaction()`, `listWalletUnspents()` | +| Keychains | `listKeychains()`, `getKeychain()`, `createBitGoKeychain()`, `createBackupKeychain()`, `addKeychain()` | +| Policies | `setVelocityPolicyRule()`, `updateVelocityPolicyRule()`, `removePolicyRule()` | +| Wallet sharing | `listWalletShares()`, `getWalletShare()`, `resendWalletShareInvite()`, `cancelWalletShare()`, `removeWalletUser()` | +| Pending approvals | `listPendingApprovals()`, `getPendingApproval()` | +| Webhooks | Wallet and user webhooks: `list…`, `add…`, `remove…` and `simulate…Webhook()`. `getWebhookPayload()` reads an incoming webhook. | +| Fees and prices | `estimateTransactionFees()`, `getMarketPriceData()` | +| Amounts | `toSatoshi()`, `toBTC()`, `toBaseUnits()`, `fromBaseUnits()` | + +### `BitGoExpress`: your BitGo Express instance -$bitgo = new BitGoSDK('YOUR_API_KEY_HERE', CurrencyCode::BITCOIN, false); -$bitgo->walletId = 'YOUR_WALLET_ID_HERE'; +| Area | Methods | +| --- | --- | +| Status and login | `ping()`, `pingExpress()`, `login()` | +| Wallets | `generateWallet()`, `addWallet()`, `createKeychain()` | +| Sending | `sendTransaction()`, `sendTransactionToMany()` | +| Build, sign and send in steps | `buildTransaction()`, `signBuildTransaction()`, `sendBuildTransaction()` | +| Unspent management | `consolidateWalletUnspents()`, `fanoutWalletUnspents()`, `sweep()` | +| Sharing and approvals | `shareWallet()`, `acceptWalletShare()`, `updatePendingApproval()` | +| Utilities | `encrypt()`, `decrypt()`, `verifyAddress()` | -$createAddress = $bitgo->createWalletAddress(); +Each method's parameters are documented in its docblock. For what each endpoint does, see the [BitGo API reference](https://developers.bitgo.com/reference/overview) and the [Express API reference](https://developers.bitgo.com/reference/express-overview). + +## Error handling + +Calls don't throw on API or network errors. They return an array with an `error` message and the HTTP `status`, plus any other fields BitGo sent: + +```php +$transfer = $bitgo->getWalletTransfer('TRANSFER_ID'); + +if (isset($transfer['error'])) { + if ($transfer['status'] === 0) { + // no response: timeout, DNS, TLS, ... + } elseif ($transfer['status'] === 404) { + // no such transfer + } else { + // any other non-2xx status, or an invalid JSON body + } +} ``` -More examples are in [`examples/`](examples/). +`buildTransaction()` and `signBuildTransaction()` return the same fields as an object. ## BitGo Express -BitGo signs transactions on the client, so sending coins, creating wallets and other calls that use your keys go through `BitGoExpress`, which talks to your own BitGo Express instance. See [Install BitGo Express](https://developers.bitgo.com/docs/get-started-express-install) to run one, and the [Express API reference](https://developers.bitgo.com/reference/express-overview) for its endpoints. +BitGo signs transactions on the client, so sending coins, creating wallets and other calls that use your keys go through `BitGoExpress`, which talks to your own BitGo Express instance. See [Install BitGo Express](https://developers.bitgo.com/docs/get-started-express-install) to run one. ### Use TLS unless Express is on the same host -Requests to Express carry wallet passphrases and private keys. Over plain `http://` that is only safe when Express listens on `localhost`. Otherwise, run Express with a TLS certificate and prefix the hostname with `https://`: +Over plain `http://`, Express is only safe when it listens on `localhost`. Otherwise, run Express with a TLS certificate and prefix the hostname with `https://`: ```php -use neto737\BitGoSDK\BitGoExpress; -use neto737\BitGoSDK\Enum\CurrencyCode; - $express = new BitGoExpress('https://express.internal', 3080, CurrencyCode::BITCOIN); // For a self-signed or private-CA certificate, trust that certificate or CA file. $express->setCaFile('/path/to/express.crt'); ``` -Certificates are verified by default. `setVerifyTls(false)` turns verification off, so use it only for local testing. +`setCaFile()` throws an `InvalidArgumentException` if the file can't be read. Certificates are verified by default. `setVerifyTls(false)` turns verification off, so use it only for local testing. ### Minimum Express version -Use BitGo Express **14.1.3 or newer**. Older versions fail on two changes BitGo made on its side: +Use BitGo Express **14.1.3 or later**. Older versions fail on two changes BitGo made on its side: -- **UTXO withdrawals from cold wallets** must use PSBT in production. BitGo needs Express 14.1.3 or newer for this ([changelog](https://developers.bitgo.com/changelog/20251007)). +- **UTXO withdrawals from cold wallets** must use PSBT in production. BitGo needs Express 14.1.3 or later for this ([changelog](https://developers.bitgo.com/changelog/20251007)). - **Send-to-many on EVM assets** (`sendTransactionToMany()` on ETH and other EVM coins) uses a new batcher contract since May 22, 2025, so Express releases from before that date can't send it ([changelog](https://developers.bitgo.com/changelog/20250522)). +## Client settings + +Both clients have these settings: + +```php +$bitgo->setTimeout(30); // request timeout in seconds. Default: 60 (BitGoSDK) or 310 (BitGoExpress) +$bitgo->setConnectTimeout(5); // connect timeout in seconds. Default: 10 +$bitgo->setForceIPv4(true); // use IPv4 only. Default: whatever the system allows +$bitgo->setCaFile('/path/to/ca.pem'); +``` + +`BitGoSDK::setRequestIP('203.0.113.10')` sends requests from that local IP. It returns `false` on testnet or for an invalid IP. + +### TLS certificates + +Both clients check certificates against your system's CA store. If PHP can't find one, they use the Mozilla CA bundle from [`composer/ca-bundle`](https://github.com/composer/ca-bundle), so keep your dependencies up to date. To trust a different CA, for example behind a TLS-inspecting proxy, call `setCaFile('/path/to/ca.pem')`. + ## Documentation - [BitGo developer portal](https://developers.bitgo.com/) -- [BitGo API reference](https://developers.bitgo.com/reference/overview) +- [BitGo API reference](https://developers.bitgo.com/reference/overview) and [Express API reference](https://developers.bitgo.com/reference/express-overview) - [Webhooks](https://developers.bitgo.com/docs/webhooks-overview) and [policies](https://developers.bitgo.com/docs/policies-overview) -## TLS certificates +**Upgrading from 2.2.x?** 2.3 is a drop-in upgrade, but two behaviours changed: `0` and `false` params are now sent, and errors come back as `['error' => …, 'status' => …]`. Read the [Changed section of CHANGELOG.md](CHANGELOG.md#changed) before you upgrade. -Both clients check certificates against your system's CA store. If PHP can't find one, they use the Mozilla CA bundle from [`composer/ca-bundle`](https://github.com/composer/ca-bundle), so keep your dependencies up to date. To trust a different CA, for example behind a TLS-inspecting proxy, call `setCaFile('/path/to/ca.pem')`. +## Contributing -## License +Bug reports and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the branch model and release process. To run the checks: -MIT. See [LICENSE](LICENSE). Versions 2.2.3 and earlier were released under GPL-3.0. +```sh +composer install +composer check # coding standard, static analysis and tests +``` + +Each check can also run on its own: `composer test` (PHPUnit), `composer analyse` (PHPStan) and `composer cs` (PHP-CS-Fixer). `composer cs:fix` fixes the coding style. The code follows PSR-12, with 4-space indentation and LF line endings. + +The tests replace the HTTP transport with a recorder, so they don't need a BitGo account or network access. + +To report a security issue, follow [SECURITY.md](SECURITY.md) instead of opening a public issue. ## Credits -- [Neto Melo](https://github.com/neto737) +Created and maintained by [Neto Melo](https://github.com/neto737). + +This is a community project. It is not affiliated with or endorsed by BitGo, Inc. -## Donate +## Support the project :heart: + +If this library saves you time, you can support its development via [Liberapay](https://liberapay.com/neto737), [PayPal](https://www.paypal.com/donate/?business=T7RVRCXLZXB58&no_recurring=0¤cy_code=USD) or crypto: + +``` +BTC: bc1pduj90df9cs3md3gym3q809slfv2x5phnpv8xznajys5q3tlulnzqt3flwn +ETH: 0xeef9220639F14E7A0FD825AAAd0574e5a8aD7A4B +LTC: ltc1q508qfkd09vyya6c5zkfx4r248pf3ezj9ngjdr2 +``` + +## License -[![Donate BTC](https://img.shields.io/badge/donate-BTC-ff9900.svg?style=for-the-badge)](https://www.blockchain.com/btc/address/bc1pduj90df9cs3md3gym3q809slfv2x5phnpv8xznajys5q3tlulnzqt3flwn) -[![Donate ETH](https://img.shields.io/badge/donate-ETH-3C3C3D.svg?style=for-the-badge)](https://etherscan.io/address/0xeef9220639F14E7A0FD825AAAd0574e5a8aD7A4B) -[![Donate LTC](https://img.shields.io/badge/donate-LTC-D3D3D3.svg?style=for-the-badge)](https://blockchair.com/litecoin/address/ltc1q508qfkd09vyya6c5zkfx4r248pf3ezj9ngjdr2) -[![Donate with PayPal](https://img.shields.io/badge/donate-PayPal-blue.svg?style=for-the-badge)](https://www.paypal.com/donate/?business=T7RVRCXLZXB58&no_recurring=0¤cy_code=USD) +Released under the [MIT License](LICENSE). Versions 2.2.3 and earlier were released under GPL-3.0. diff --git a/composer.json b/composer.json index 247c58d..31abfe1 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { "name": "neto737/bitgosdk-php", - "description": "A BitGo SDK written in PHP", + "description": "PHP client for the BitGo API and BitGo Express: wallets, addresses, transfers, webhooks and sending coins.", "type": "library", - "keywords": ["bitgo", "sdk", "bitcoin"], + "keywords": ["bitgo", "bitgo-express", "sdk", "api-client", "bitcoin", "ethereum", "cryptocurrency", "wallet", "blockchain", "payments"], "homepage": "https://github.com/neto737/BitGoSDK-PHP", "license": "MIT", @@ -16,9 +16,21 @@ "support": { "issues": "https://github.com/neto737/BitGoSDK-PHP/issues", "source": "https://github.com/neto737/BitGoSDK-PHP", - "security": "https://github.com/neto737/BitGoSDK-PHP/security/policy" + "security": "https://github.com/neto737/BitGoSDK-PHP/security/policy", + "docs": "https://developers.bitgo.com/" }, + "funding": [ + { + "type": "liberapay", + "url": "https://liberapay.com/neto737" + }, + { + "type": "custom", + "url": "https://www.paypal.com/donate/?business=T7RVRCXLZXB58&no_recurring=0¤cy_code=USD" + } + ], + "autoload": { "psr-4": { "neto737\\BitGoSDK\\": "src/" @@ -32,8 +44,9 @@ }, "require": { - "php": ">=7.4", + "php": "^7.4 || ^8.0", "ext-curl": "*", + "ext-json": "*", "composer/ca-bundle": "^1.5" }, @@ -47,14 +60,20 @@ "test": "phpunit", "analyse": "phpstan analyse", "cs": "php-cs-fixer fix --dry-run --diff", - "cs:fix": "php-cs-fixer fix" + "cs:fix": "php-cs-fixer fix", + "check": [ + "@cs", + "@analyse", + "@test" + ] }, "scripts-descriptions": { "test": "Run the PHPUnit test suite.", "analyse": "Run PHPStan static analysis.", "cs": "Check the coding standard without changing files.", - "cs:fix": "Fix coding standard violations." + "cs:fix": "Fix coding standard violations.", + "check": "Run the coding standard check, static analysis and tests." }, "config": {