Skip to content

Commit 6ec92c6

Browse files
authored
Merge pull request #168 from coinbase/cleanup_staking
Cleanup staking operation + wallet address related code
2 parents e61a4b4 + eb064c1 commit 6ec92c6

File tree

2 files changed

+126
-122
lines changed

2 files changed

+126
-122
lines changed

src/coinbase/address/wallet_address.ts

Lines changed: 115 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ export class WalletAddress extends Address {
7575
this.key = key;
7676
}
7777

78+
/**
79+
* Returns whether the Address has a private key backing it to sign transactions.
80+
*
81+
* @returns Whether the Address has a private key backing it to sign transactions.
82+
*/
83+
public canSign(): boolean {
84+
return !!this.key;
85+
}
86+
7887
/**
7988
* Returns all the trades associated with the address.
8089
*
@@ -185,7 +194,7 @@ export class WalletAddress extends Address {
185194
gasless: gasless,
186195
};
187196

188-
let response = await Coinbase.apiClients.transfer!.createTransfer(
197+
const response = await Coinbase.apiClients.transfer!.createTransfer(
189198
this.getWalletId(),
190199
this.getId(),
191200
createTransferRequest,
@@ -212,38 +221,10 @@ export class WalletAddress extends Address {
212221
throw new Error("Transfer timed out");
213222
}
214223

215-
/**
216-
* Returns the address and network ID of the given destination.
217-
*
218-
* @param destination - The destination to get the address and network ID of.
219-
* @returns The address and network ID of the destination.
220-
*/
221-
private getDestinationAddressAndNetwork(destination: Destination): [string, string] {
222-
if (typeof destination !== "string" && destination.getNetworkId() !== this.getNetworkId()) {
223-
throw new ArgumentError("Transfer must be on the same Network");
224-
}
225-
if (destination instanceof WalletClass) {
226-
return [destination.getDefaultAddress()!.getId(), destination.getNetworkId()];
227-
}
228-
if (destination instanceof Address) {
229-
return [destination.getId(), destination.getNetworkId()];
230-
}
231-
return [destination, this.getNetworkId()];
232-
}
233-
234-
/**
235-
* Returns whether the Address has a private key backing it to sign transactions.
236-
*
237-
* @returns Whether the Address has a private key backing it to sign transactions.
238-
*/
239-
public canSign(): boolean {
240-
return !!this.key;
241-
}
242-
243224
/**
244225
* Trades the given amount of the given Asset for another Asset. Only same-network Trades are supported.
245226
*
246-
* @param options = The options to create the Trade.
227+
* @param options - The options to create the Trade.
247228
* @param options.amount - The amount of the From Asset to send.
248229
* @param options.fromAssetId - The ID of the Asset to trade from.
249230
* @param options.toAssetId - The ID of the Asset to trade to.
@@ -291,82 +272,6 @@ export class WalletAddress extends Address {
291272
throw new Error("Trade timed out");
292273
}
293274

294-
/**
295-
* Creates a trade model for the specified amount and assets.
296-
*
297-
* @param amount - The amount of the Asset to send.
298-
* @param fromAsset - The Asset to trade from.
299-
* @param toAsset - The Asset to trade to.
300-
* @returns A promise that resolves to a Trade object representing the new trade.
301-
*/
302-
private async createTradeRequest(
303-
amount: Amount,
304-
fromAsset: Asset,
305-
toAsset: Asset,
306-
): Promise<Trade> {
307-
const tradeRequestPayload = {
308-
amount: fromAsset.toAtomicAmount(new Decimal(amount.toString())).toString(),
309-
from_asset_id: fromAsset.primaryDenomination(),
310-
to_asset_id: toAsset.primaryDenomination(),
311-
};
312-
const tradeModel = await Coinbase.apiClients.trade!.createTrade(
313-
this.getWalletId(),
314-
this.getId(),
315-
tradeRequestPayload,
316-
);
317-
return new Trade(tradeModel?.data);
318-
}
319-
320-
/**
321-
* Broadcasts a trade using the provided signed payloads.
322-
*
323-
* @param trade - The Trade object representing the trade.
324-
* @param signedPayload - The signed payload of the trade.
325-
* @param approveTransactionPayload - The signed payload of the approval transaction, if any.
326-
* @returns A promise that resolves to a Trade object representing the broadcasted trade.
327-
*/
328-
private async broadcastTradeRequest(
329-
trade: Trade,
330-
signedPayload: string,
331-
approveTransactionPayload?: string,
332-
): Promise<Trade> {
333-
const broadcastTradeRequestPayload = {
334-
signed_payload: signedPayload,
335-
approve_transaction_signed_payload: approveTransactionPayload
336-
? approveTransactionPayload
337-
: undefined,
338-
};
339-
340-
const response = await Coinbase.apiClients.trade!.broadcastTrade(
341-
this.getWalletId(),
342-
this.getId(),
343-
trade.getId(),
344-
broadcastTradeRequestPayload,
345-
);
346-
347-
return new Trade(response.data);
348-
}
349-
350-
/**
351-
* Checks if trading is possible and raises an error if not.
352-
*
353-
* @param amount - The amount of the Asset to send.
354-
* @param fromAssetId - The ID of the Asset to trade from. For Ether, eth, gwei, and wei are supported.
355-
* @throws {Error} If the private key is not loaded, or if the asset IDs are unsupported, or if there are insufficient funds.
356-
*/
357-
private async validateCanTrade(amount: Amount, fromAssetId: string) {
358-
if (!Coinbase.useServerSigner && !this.key) {
359-
throw new Error("Cannot trade from address without private key loaded");
360-
}
361-
const currentBalance = await this.getBalance(fromAssetId);
362-
amount = new Decimal(amount.toString());
363-
if (currentBalance.lessThan(amount)) {
364-
throw new Error(
365-
`Insufficient funds: ${amount} requested, but only ${currentBalance} available`,
366-
);
367-
}
368-
}
369-
370275
/**
371276
* Creates a staking operation to stake.
372277
*
@@ -460,6 +365,101 @@ export class WalletAddress extends Address {
460365
);
461366
}
462367

368+
/**
369+
* Returns the address and network ID of the given destination.
370+
*
371+
* @param destination - The destination to get the address and network ID of.
372+
* @returns The address and network ID of the destination.
373+
*/
374+
private getDestinationAddressAndNetwork(destination: Destination): [string, string] {
375+
if (typeof destination !== "string" && destination.getNetworkId() !== this.getNetworkId()) {
376+
throw new ArgumentError("Transfer must be on the same Network");
377+
}
378+
if (destination instanceof WalletClass) {
379+
return [destination.getDefaultAddress()!.getId(), destination.getNetworkId()];
380+
}
381+
if (destination instanceof Address) {
382+
return [destination.getId(), destination.getNetworkId()];
383+
}
384+
return [destination, this.getNetworkId()];
385+
}
386+
387+
/**
388+
* Creates a trade model for the specified amount and assets.
389+
*
390+
* @param amount - The amount of the Asset to send.
391+
* @param fromAsset - The Asset to trade from.
392+
* @param toAsset - The Asset to trade to.
393+
* @returns A promise that resolves to a Trade object representing the new trade.
394+
*/
395+
private async createTradeRequest(
396+
amount: Amount,
397+
fromAsset: Asset,
398+
toAsset: Asset,
399+
): Promise<Trade> {
400+
const tradeRequestPayload = {
401+
amount: fromAsset.toAtomicAmount(new Decimal(amount.toString())).toString(),
402+
from_asset_id: fromAsset.primaryDenomination(),
403+
to_asset_id: toAsset.primaryDenomination(),
404+
};
405+
const tradeModel = await Coinbase.apiClients.trade!.createTrade(
406+
this.getWalletId(),
407+
this.getId(),
408+
tradeRequestPayload,
409+
);
410+
return new Trade(tradeModel?.data);
411+
}
412+
413+
/**
414+
* Broadcasts a trade using the provided signed payloads.
415+
*
416+
* @param trade - The Trade object representing the trade.
417+
* @param signedPayload - The signed payload of the trade.
418+
* @param approveTransactionPayload - The signed payload of the approval transaction, if any.
419+
* @returns A promise that resolves to a Trade object representing the broadcasted trade.
420+
*/
421+
private async broadcastTradeRequest(
422+
trade: Trade,
423+
signedPayload: string,
424+
approveTransactionPayload?: string,
425+
): Promise<Trade> {
426+
const broadcastTradeRequestPayload = {
427+
signed_payload: signedPayload,
428+
approve_transaction_signed_payload: approveTransactionPayload
429+
? approveTransactionPayload
430+
: undefined,
431+
};
432+
433+
const response = await Coinbase.apiClients.trade!.broadcastTrade(
434+
this.getWalletId(),
435+
this.getId(),
436+
trade.getId(),
437+
broadcastTradeRequestPayload,
438+
);
439+
440+
return new Trade(response.data);
441+
}
442+
443+
/**
444+
* Checks if trading is possible and raises an error if not.
445+
*
446+
* @param amount - The amount of the Asset to send.
447+
* @param fromAssetId - The ID of the Asset to trade from. For Ether, eth, gwei, and wei are supported.
448+
* @throws {Error} If the private key is not loaded, or if the asset IDs are unsupported, or if there are insufficient funds.
449+
*/
450+
private async validateCanTrade(amount: Amount, fromAssetId: string) {
451+
if (!Coinbase.useServerSigner && !this.key) {
452+
throw new Error("Cannot trade from address without private key loaded");
453+
}
454+
const currentBalance = await this.getBalance(fromAssetId);
455+
amount = new Decimal(amount.toString());
456+
if (currentBalance.lessThan(amount)) {
457+
throw new Error(
458+
`Insufficient funds: ${amount} requested, but only ${currentBalance} available`,
459+
);
460+
}
461+
}
462+
463463
/**
464464
* Creates a staking operation to stake, signs it, and broadcasts it on the blockchain.
465465
*
@@ -470,6 +470,8 @@ export class WalletAddress extends Address {
470470
* @param options - Additional options such as setting the mode for the staking action.
471471
* @param timeoutSeconds - The amount to wait for the transaction to complete when broadcasted.
472472
* @param intervalSeconds - The amount to check each time for a successful broadcast.
473+
* @throws {APIError} if the API request to create or broadcast staking operation fails.
474+
* @throws {Error} if the amount is less than zero.
473475
* @returns The staking operation after it's completed fully.
474476
*/
475477
private async createStakingOperation(
@@ -501,7 +503,7 @@ export class WalletAddress extends Address {
501503
continue;
502504
}
503505
stakingOperation = await this.broadcastStakingOperationRequest(
504-
stakingOperation,
506+
stakingOperation.getID(),
505507
transaction.getSignedPayload()!.slice(2),
506508
i,
507509
);
@@ -531,7 +533,7 @@ export class WalletAddress extends Address {
531533
* @param mode - The staking mode. Defaults to DEFAULT.
532534
* @param options - Additional options such as setting the mode for the staking action.
533535
* @private
534-
* @throws {Error} if the amount is less than zero.
536+
* @throws {APIError} if the API request to create staking operation fails.
535537
* @returns The created staking operation.
536538
*/
537539
private async createStakingOperationRequest(
@@ -565,28 +567,29 @@ export class WalletAddress extends Address {
565567
/**
566568
* A helper function that broadcasts the signed payload.
567569
*
568-
* @param stakingOperation - The staking operation related to the signed payload.
570+
* @param stakingOperationID - The staking operation related to the signed payload.
569571
* @param signedPayload - The payload that's being broadcasted.
570572
* @param transactionIndex - The index of the transaction in the array from the staking operation.
571573
* @private
572574
* @returns An updated staking operation with the broadcasted transaction.
573575
*/
574576
private async broadcastStakingOperationRequest(
575-
stakingOperation: StakingOperation,
577+
stakingOperationID: string,
576578
signedPayload: string,
577579
transactionIndex: number,
578580
): Promise<StakingOperation> {
579581
const broadcastStakingOperationRequest = {
580582
signed_payload: signedPayload,
581583
transaction_index: transactionIndex,
582584
};
585+
583586
const response = await Coinbase.apiClients.stake!.broadcastStakingOperation(
584587
this.getWalletId(),
585588
this.getId(),
586-
stakingOperation.getID(),
589+
stakingOperationID,
587590
broadcastStakingOperationRequest,
588591
);
589592

590-
return new StakingOperation(response!.data);
593+
return new StakingOperation(response.data);
591594
}
592595
}

src/coinbase/staking_operation.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Coinbase } from "./coinbase";
88
import { delay } from "./utils";
99

1010
/**
11-
* A representation of a staking operation (stake, unstake, claim rewards, etc). It
11+
* A representation of a staking operation (stake, unstake, claim stake, etc.). It
1212
* may have multiple steps with some being transactions to sign, and others to wait.
1313
*/
1414
export class StakingOperation {
@@ -25,6 +25,7 @@ export class StakingOperation {
2525
if (!model) {
2626
throw new Error("Invalid model type");
2727
}
28+
2829
this.model = model;
2930
this.transactions = [];
3031

@@ -44,6 +45,15 @@ export class StakingOperation {
4445
return this.model.id;
4546
}
4647

48+
/**
49+
* Get the status of the staking operation.
50+
*
51+
* @returns The status of the staking operation.
52+
*/
53+
public getStatus(): StakingOperationStatusEnum {
54+
return this.model.status;
55+
}
56+
4757
/**
4858
* Returns the Wallet ID if it exists.
4959
*
@@ -62,15 +72,6 @@ export class StakingOperation {
6272
return this.model.address_id;
6373
}
6474

65-
/**
66-
* Get the status of the staking operation.
67-
*
68-
* @returns The status of the staking operation.
69-
*/
70-
public getStatus(): StakingOperationStatusEnum {
71-
return this.model.status;
72-
}
73-
7475
/**
7576
* Reloads the StakingOperation model with the latest data from the server.
7677
* If the StakingOperation object was created by an ExternalAddress then it will

0 commit comments

Comments
 (0)