Skip to content

Commit cb86fbe

Browse files
committed
feat: send digital asset
TICKET: COIN-3820
1 parent cdfe7c3 commit cb86fbe

File tree

8 files changed

+122
-54
lines changed

8 files changed

+122
-54
lines changed

examples/ts/nft/get-wallet-nfts.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Get the NFT balance of a wallet at BitGo.
3+
* This makes use of the convenience function wallets().get()
4+
*
5+
* Copyright 2025, BitGo, Inc. All Rights Reserved.
6+
*/
7+
import { BitGoAPI } from '@bitgo/sdk-api';
8+
import { Tapt } from "@bitgo/sdk-coin-apt";
9+
require('dotenv').config({ path: '../../../.env' });
10+
11+
const bitgo = new BitGoAPI({
12+
accessToken: '',
13+
env: 'test',
14+
});
15+
16+
const coin = 'tapt';
17+
bitgo.register(coin, Tapt.createInstance);
18+
19+
const walletId = '';
20+
21+
async function main() {
22+
const wallet = await bitgo.coin(coin).wallets().get({ id: walletId, allTokens: true });
23+
24+
console.log('\nWallet ID:', wallet.id());
25+
console.log('\nSupported NFTs:', );
26+
console.log(wallet.nftBalances());
27+
console.log('\nUnsupported NFTs:', );
28+
console.log(wallet.unsupportedNftBalances());
29+
}
30+
31+
main().catch((e) => console.error(e));

examples/ts/nft/send-wallet-nfts.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Send an NFT of a wallet at BitGo.
3+
*
4+
* Copyright 2025, BitGo, Inc. All Rights Reserved.
5+
*/
6+
import { BitGoAPI } from '@bitgo/sdk-api';
7+
import { Tapt } from "@bitgo/sdk-coin-apt";
8+
import { TokenType } from "@bitgo/sdk-core";
9+
10+
require('dotenv').config({ path: '../../../.env' });
11+
12+
const bitgo = new BitGoAPI({
13+
accessToken: '',
14+
env: 'test',
15+
});
16+
17+
const coin = 'tapt';
18+
bitgo.register(coin, Tapt.createInstance);
19+
20+
const walletId = '';
21+
22+
async function main() {
23+
bitgo.unlock({ otp: '000000' });
24+
const wallet = await bitgo.coin(coin).wallets().get({id: walletId, allTokens: true});
25+
26+
console.log('\nWallet ID:', wallet.id());
27+
console.log('\nSupported NFTs:', );
28+
console.log(wallet.nftBalances());
29+
30+
const resp = await wallet.sendNft({
31+
walletPassphrase: '',
32+
type: 'transfer',
33+
}, {
34+
type: TokenType.DIGITAL_ASSET,
35+
tokenId: '0x6dac741daaf6ee98d91b0d88df7e169b0110f2e3b14b998896aa36c8e54b3449',
36+
tokenContractAddress: '0xbbc561fbfa5d105efd8dfb06ae3e7e5be46331165b99d518f094c701e40603b5',
37+
recipientAddress: '0x9fef749050644625012a2c866973775e7123753b3eef0a1a4037453ac26d79bf',
38+
});
39+
console.log('\nSend NFT Response:', resp);
40+
}
41+
42+
main().catch((e) => console.error(e));

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

+3
Original file line numberDiff line numberDiff line change
@@ -2728,6 +2728,9 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
27282728

27292729
return transferBuilder.build();
27302730
}
2731+
2732+
default:
2733+
throw new Error(`Unsupported NFT type: ${params.type}`);
27312734
}
27322735
}
27332736

modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { IWebhooks } from '../webhook/iWebhooks';
1414
import { TransactionType } from '../../account-lib';
1515
import { IInscriptionBuilder } from '../inscriptionBuilder';
1616
import { Hash } from 'crypto';
17-
import { MPCTx, PopulatedIntent } from '../utils';
17+
import { MPCTx, PopulatedIntent, TokenType } from '../utils';
1818

1919
export const multisigTypes = {
2020
onchain: 'onchain',
@@ -449,7 +449,7 @@ export type NFTTransferOptions = {
449449
recipientAddress: string;
450450
} & (
451451
| {
452-
type: 'ERC721';
452+
type: 'ERC721' | TokenType.DIGITAL_ASSET;
453453
tokenId: string;
454454
}
455455
| {

modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ export enum TokenType {
148148
ERC721 = 'ERC721',
149149
ERC1155 = 'ERC1155',
150150
ERC20 = 'ERC20',
151+
DIGITAL_ASSET = 'Digital Asset',
151152
}
152153
export interface TokenTransferRecipientParams {
153154
tokenType: TokenType;

modules/sdk-core/src/bitgo/wallet/iWallet.ts

+1
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ export interface SendManyOptions extends PrebuildAndSignTransactionOptions {
646646
feeLimit?: string;
647647
data?: string;
648648
tokenName?: string;
649+
tokenData?: TokenTransferRecipientParams;
649650
}[];
650651
numBlocks?: number;
651652
feeRate?: number;

modules/sdk-core/src/bitgo/wallet/wallet.ts

+38-12
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,30 @@ import { decryptKeychainPrivateKey, Keychain, KeychainWithEncryptedPrv } from '.
2525
import { IPendingApproval, PendingApproval, PendingApprovals } from '../pendingApproval';
2626
import { TradingAccount } from '../trading';
2727
import {
28-
inferAddressType,
29-
RequestTracer,
30-
TxRequest,
3128
EddsaUnsignedTransaction,
29+
inferAddressType,
3230
IntentOptionsForMessage,
3331
IntentOptionsForTypedData,
32+
RequestTracer,
3433
RequestType,
34+
TokenTransferRecipientParams,
35+
TokenType,
36+
TxRequest,
3537
} from '../utils';
3638
import {
3739
AccelerateTransactionOptions,
3840
AddressesOptions,
3941
BuildConsolidationTransactionOptions,
4042
BuildTokenEnablementOptions,
43+
BulkCreateShareOption,
44+
BulkWalletShareKeychain,
45+
BulkWalletShareOptions,
4146
ChangeFeeOptions,
4247
ConsolidateUnspentsOptions,
4348
CreateAddressOptions,
49+
CreateBulkWalletShareListResponse,
4450
CreatePolicyRuleOptions,
4551
CreateShareOptions,
46-
BulkCreateShareOption,
47-
BulkWalletShareOptions,
4852
CrossChainUTXO,
4953
DeployForwardersOptions,
5054
DownloadKeycardOptions,
@@ -54,13 +58,15 @@ import {
5458
ForwarderBalance,
5559
ForwarderBalanceOptions,
5660
FreezeOptions,
61+
FundForwarderParams,
5762
FundForwardersOptions,
5863
GetAddressOptions,
5964
GetPrvOptions,
6065
GetTransactionOptions,
6166
GetTransferOptions,
6267
GetUserPrvOptions,
6368
IWallet,
69+
ManageUnspentReservationOptions,
6470
MaximumSpendable,
6571
MaximumSpendableOptions,
6672
ModifyWebhookOptions,
@@ -76,7 +82,9 @@ import {
7682
SendNFTOptions,
7783
SendNFTResult,
7884
SendOptions,
85+
SharedKeyChain,
7986
ShareWalletOptions,
87+
SignAndSendTxRequestOptions,
8088
SimulateWebhookOptions,
8189
SubmitTransactionOptions,
8290
SubWalletType,
@@ -94,12 +102,6 @@ import {
94102
WalletSignTransactionOptions,
95103
WalletSignTypedDataOptions,
96104
WalletType,
97-
CreateBulkWalletShareListResponse,
98-
SharedKeyChain,
99-
BulkWalletShareKeychain,
100-
ManageUnspentReservationOptions,
101-
SignAndSendTxRequestOptions,
102-
FundForwarderParams,
103105
} from './iWallet';
104106
import { GoStakingWallet, StakingWallet } from '../staking';
105107
import EddsaUtils from '../utils/tss/eddsa';
@@ -2405,7 +2407,7 @@ export class Wallet implements IWallet {
24052407
if (!this.baseCoin.isValidAddress(recipientAddress)) {
24062408
throw new Error(`Invalid recipient address ${recipientAddress}`);
24072409
}
2408-
const baseAddress = this.coinSpecific()?.baseAddress;
2410+
const baseAddress = this.coinSpecific()?.baseAddress || this.coinSpecific()?.rootAddress;
24092411
if (!baseAddress) {
24102412
throw new Error('Missing base address for wallet');
24112413
}
@@ -2463,6 +2465,30 @@ export class Wallet implements IWallet {
24632465
],
24642466
});
24652467
}
2468+
2469+
case TokenType.DIGITAL_ASSET: {
2470+
if (!nftBalance.collections[sendNftOptions.tokenId]) {
2471+
throw new Error(
2472+
`Token ${sendNftOptions.tokenId} not found in collection ${tokenContractAddress} or does not have a spendable balance`
2473+
);
2474+
}
2475+
const tokenData: TokenTransferRecipientParams = {
2476+
tokenType: sendNftOptions.type,
2477+
tokenQuantity: '1', // This NFT standard will always have quantity of 1
2478+
tokenContractAddress,
2479+
tokenId: sendNftOptions.tokenId,
2480+
};
2481+
return this.sendMany({
2482+
...sendOptions,
2483+
recipients: [
2484+
{
2485+
address: recipientAddress,
2486+
amount: '1', // the amount needs to be non-zero for the transaction to be valid, it is ignored
2487+
tokenData,
2488+
},
2489+
],
2490+
});
2491+
}
24662492
}
24672493
}
24682494

yarn.lock

+4-40
Original file line numberDiff line numberDiff line change
@@ -12148,18 +12148,7 @@ html-minifier-terser@^6.0.2:
1214812148
tapable "^1.1.3"
1214912149
util.promisify "1.0.0"
1215012150

12151-
"html-webpack-plugin-5@npm:html-webpack-plugin@^5":
12152-
version "5.6.3"
12153-
resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz#a31145f0fee4184d53a794f9513147df1e653685"
12154-
integrity sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==
12155-
dependencies:
12156-
"@types/html-minifier-terser" "^6.0.0"
12157-
html-minifier-terser "^6.0.2"
12158-
lodash "^4.17.21"
12159-
pretty-error "^4.0.0"
12160-
tapable "^2.0.0"
12161-
12162-
html-webpack-plugin@^5.5.0:
12151+
"html-webpack-plugin-5@npm:html-webpack-plugin@^5", html-webpack-plugin@^5.5.0:
1216312152
version "5.6.3"
1216412153
resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz#a31145f0fee4184d53a794f9513147df1e653685"
1216512154
integrity sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==
@@ -18783,16 +18772,7 @@ string-argv@^0.3.1:
1878318772
resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
1878418773
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
1878518774

18786-
"string-width-cjs@npm:string-width@^4.2.0":
18787-
version "4.2.3"
18788-
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
18789-
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
18790-
dependencies:
18791-
emoji-regex "^8.0.0"
18792-
is-fullwidth-code-point "^3.0.0"
18793-
strip-ansi "^6.0.1"
18794-
18795-
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
18775+
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
1879618776
version "4.2.3"
1879718777
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
1879818778
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -18868,7 +18848,7 @@ string_decoder@~1.1.1:
1886818848
dependencies:
1886918849
safe-buffer "~5.1.0"
1887018850

18871-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
18851+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
1887218852
version "6.0.1"
1887318853
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
1887418854
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -18882,13 +18862,6 @@ strip-ansi@^3.0.1:
1888218862
dependencies:
1888318863
ansi-regex "^2.0.0"
1888418864

18885-
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
18886-
version "6.0.1"
18887-
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
18888-
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
18889-
dependencies:
18890-
ansi-regex "^5.0.1"
18891-
1889218865
strip-ansi@^7.0.1:
1889318866
version "7.1.0"
1889418867
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -20754,7 +20727,7 @@ [email protected]:
2075420727
resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"
2075520728
integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==
2075620729

20757-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
20730+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
2075820731
version "7.0.0"
2075920732
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
2076020733
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -20772,15 +20745,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
2077220745
string-width "^4.1.0"
2077320746
strip-ansi "^6.0.0"
2077420747

20775-
wrap-ansi@^7.0.0:
20776-
version "7.0.0"
20777-
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
20778-
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
20779-
dependencies:
20780-
ansi-styles "^4.0.0"
20781-
string-width "^4.1.0"
20782-
strip-ansi "^6.0.0"
20783-
2078420748
wrap-ansi@^8.1.0:
2078520749
version "8.1.0"
2078620750
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"

0 commit comments

Comments
 (0)