Skip to content

Commit 2acd35d

Browse files
authored
Merge pull request #5988 from BitGo/coin-3820-send-nft
feat: send digital asset
2 parents e806070 + e4f9135 commit 2acd35d

File tree

7 files changed

+118
-14
lines changed

7 files changed

+118
-14
lines changed

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

Lines changed: 31 additions & 0 deletions
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

Lines changed: 42 additions & 0 deletions
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: '',
36+
tokenContractAddress: '',
37+
recipientAddress: '',
38+
});
39+
console.log('\nSend NFT Response:', resp);
40+
}
41+
42+
main().catch((e) => console.error(e));

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

Lines changed: 3 additions & 0 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 38 additions & 12 deletions
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

0 commit comments

Comments
 (0)