33 BigNum ,
44 Certificate ,
55 Certificates ,
6- CoinSelectionStrategyCIP2 ,
76 Ed25519KeyHash ,
87 Ed25519Signature ,
98 hash_transaction ,
@@ -12,6 +11,7 @@ import {
1211 RewardAddress ,
1312 StakeCredential ,
1413 StakeDelegation ,
14+ StakeDeregistration ,
1515 StakeRegistration ,
1616 Transaction ,
1717 TransactionBuilder ,
@@ -20,13 +20,12 @@ import {
2020 TransactionInput ,
2121 TransactionOutput ,
2222 TransactionOutputs ,
23- TransactionUnspentOutput ,
24- TransactionUnspentOutputs ,
2523 TransactionWitnessSet ,
2624 Value ,
2725 Vkey ,
2826 Vkeywitness ,
29- Vkeywitnesses , Withdrawals ,
27+ Vkeywitnesses ,
28+ Withdrawals ,
3029} from '@emurgo/cardano-serialization-lib-nodejs' ;
3130import { Service } from "./service" ;
3231import { AdaStakeOptions , InternalAdaConfig , UTXO } from "../types/ada" ;
@@ -46,6 +45,7 @@ const CARDANO_PARAMS = {
4645 POOL_DEPOSIT : '500000000' ,
4746 KEY_DEPOSIT : '2000000' ,
4847 MIN_UTXO_VALUE_ADA_ONLY : 1000000 ,
48+ DEFAULT_NATIVE_FEES : 300000 , // Over-estimate (0.3 ADA)
4949} ;
5050
5151export class AdaService extends Service {
@@ -73,10 +73,9 @@ export class AdaService extends Service {
7373
7474 try {
7575 const utxos = await this . getUtxos ( walletAddress ) ;
76- const outputs = this . prepareTx ( CARDANO_PARAMS . KEY_DEPOSIT , walletAddress ) ;
7776 const address = await this . client . addresses ( walletAddress ) ;
7877 if ( ! address . stake_address ) {
79- throw Error ( 'No stake address' ) ;
78+ throw Error ( 'Could not fetch stake address' ) ;
8079 }
8180
8281 const stakeKeyHash = await this . getStakeKeyHash ( address . stake_address ) ;
@@ -85,12 +84,13 @@ export class AdaService extends Service {
8584 }
8685 const certificates = Certificates . new ( ) ;
8786
88- const registrations = await this . client . accountsRegistrations ( address . stake_address ) ;
87+ const registrations = await this . client . accountsRegistrationsAll ( address . stake_address ) ;
88+ const lastRegistration = registrations . length > 0 ? registrations [ registrations . length - 1 ] : undefined ;
8989 const pool = await this . client . poolsById ( poolId ) ;
9090 const poolKeyHash = Ed25519KeyHash . from_hex ( pool . hex ) ;
9191
92- // Register stake key if not done already
93- if ( registrations . length === 0 ) {
92+ // Register stake key if not done already or if last registration was a deregister action
93+ if ( ! lastRegistration || lastRegistration . action === 'deregistered' ) {
9494 certificates . add (
9595 Certificate . new_stake_registration (
9696 StakeRegistration . new (
@@ -111,6 +111,10 @@ export class AdaService extends Service {
111111 ) ,
112112 ) ,
113113 ) ;
114+
115+ const walletBalance = this . getWalletBalance ( utxos ) ;
116+ const outAmount = ( walletBalance - CARDANO_PARAMS . DEFAULT_NATIVE_FEES - Number ( CARDANO_PARAMS . KEY_DEPOSIT ) ) . toString ( ) ;
117+ const outputs = this . prepareTx ( outAmount , walletAddress ) ;
114118 return await this . buildTx ( walletAddress , utxos , outputs , certificates ) ;
115119 } catch ( error ) {
116120 throw error ;
@@ -147,27 +151,69 @@ export class AdaService extends Service {
147151 throw Error ( 'Could not retrieve rewards address' ) ;
148152 }
149153
150- const rewardsHistory = await this . client . accountsRewardsAll ( address . stake_address ) ;
151- let totalRewards : number = 0 ;
152- for ( const rewards of rewardsHistory ) {
153- totalRewards += Number ( rewards . amount ) ;
154+ const availableRewards = await this . getAvailableRewards ( address . stake_address ) ;
155+ const amountToWithdrawLovelace = amountToWithdraw ? this . adaToLovelace ( amountToWithdraw . toString ( ) ) : availableRewards . toString ( ) ;
156+ withdrawals . insert ( rewardAddress , BigNum . from_str ( amountToWithdrawLovelace ) ) ;
157+
158+ const walletBalance = this . getWalletBalance ( utxos ) ;
159+ const outAmount = ( walletBalance - CARDANO_PARAMS . DEFAULT_NATIVE_FEES + availableRewards ) . toString ( ) ;
160+ const outputs = this . prepareTx ( outAmount , walletAddress ) ;
161+
162+ return await this . buildTx ( walletAddress , utxos , outputs , null , withdrawals ) ;
163+ } catch ( error ) {
164+ throw error ;
165+ }
166+ }
167+
168+ /**
169+ * Craft ada undelegate transaction
170+ * @param walletAddress wallet delegating that will receive the rewards
171+ */
172+ async craftUnstakeTx (
173+ walletAddress : string ,
174+ ) : Promise < Transaction > {
175+
176+
177+ try {
178+ const utxos = await this . getUtxos ( walletAddress ) ;
179+ const address = await this . client . addresses ( walletAddress ) ;
180+ if ( ! address . stake_address ) {
181+ throw Error ( 'No stake address' ) ;
154182 }
155183
156- const amountToWithdrawLovelace = amountToWithdraw ? this . adaToLovelace ( amountToWithdraw . toString ( ) ) : totalRewards . toString ( ) ;
157- withdrawals . insert ( rewardAddress , BigNum . from_str ( amountToWithdrawLovelace ) ) ;
184+ const stakeKeyHash = await this . getStakeKeyHash ( address . stake_address ) ;
185+ if ( ! stakeKeyHash ) {
186+ throw Error ( 'Could not hash stake key' ) ;
187+ }
158188
159- let walletBalance = 0 ;
160- for ( const utxo of utxos ) {
161- if ( utxo . amount . length > 0 && utxo . amount [ 0 ] . unit === 'lovelace' ) {
162- walletBalance += Number ( utxo . amount [ 0 ] . quantity ) ;
163- }
189+ const withdrawals = Withdrawals . new ( ) ;
190+ const rewardAddress = RewardAddress . from_address ( Address . from_bech32 ( address . stake_address ) ) ;
191+
192+ if ( ! rewardAddress ) {
193+ throw Error ( 'Could not retrieve rewards address' ) ;
164194 }
165- // Not sure about this value (might need to be BALANCE + REWARDS + FEES)
166- const outAmount = ( CARDANO_PARAMS . MIN_UTXO_VALUE_ADA_ONLY + totalRewards ) . toString ( ) ;
195+
196+
197+ const availableRewards = await this . getAvailableRewards ( address . stake_address ) ;
198+ withdrawals . insert ( rewardAddress , BigNum . from_str ( availableRewards . toString ( ) ) ) ;
199+
200+ const walletBalance = this . getWalletBalance ( utxos ) ;
201+ const outAmount = ( walletBalance - CARDANO_PARAMS . DEFAULT_NATIVE_FEES + Number ( CARDANO_PARAMS . KEY_DEPOSIT ) + availableRewards ) . toString ( ) ;
167202 const outputs = this . prepareTx ( outAmount , walletAddress ) ;
168203
169- const tx = await this . buildTx ( walletAddress , utxos , outputs , null , withdrawals ) ;
170- return tx ;
204+ // Deregister certificate
205+ const certificates = Certificates . new ( ) ;
206+ certificates . add (
207+ Certificate . new_stake_deregistration (
208+ StakeDeregistration . new (
209+ StakeCredential . from_keyhash (
210+ Ed25519KeyHash . from_bytes ( stakeKeyHash ) ,
211+ ) ,
212+ ) ,
213+ ) ,
214+ ) ;
215+
216+ return await this . buildTx ( walletAddress , utxos , outputs , certificates , withdrawals ) ;
171217 } catch ( error ) {
172218 throw error ;
173219 }
@@ -176,15 +222,15 @@ export class AdaService extends Service {
176222 /**
177223 * Prepare outputs (destination addresses and amounts) for a transaction
178224 * @param lovelaceValue
179- * @param paymentAddress
225+ * @param toAddress
180226 * @private
181227 */
182- private prepareTx ( lovelaceValue : string , paymentAddress : string ) : TransactionOutputs {
228+ private prepareTx ( lovelaceValue : string , toAddress : string ) : TransactionOutputs {
183229 const outputs = TransactionOutputs . new ( ) ;
184230
185231 outputs . add (
186232 TransactionOutput . new (
187- Address . from_bech32 ( paymentAddress ) ,
233+ Address . from_bech32 ( toAddress ) ,
188234 Value . new ( BigNum . from_str ( lovelaceValue ) ) ,
189235 ) ,
190236 ) ;
@@ -194,15 +240,15 @@ export class AdaService extends Service {
194240
195241 /**
196242 * Build transaction with correct fees, inputs, outputs and certificates
197- * @param changeAddress
243+ * @param inputAddress
198244 * @param utxos
199245 * @param outputs
200246 * @param certificates
201247 * @param withdrawals
202248 * @private
203249 */
204250 private async buildTx (
205- changeAddress : string ,
251+ inputAddress : string ,
206252 utxos : UTXO ,
207253 outputs : TransactionOutputs ,
208254 certificates : Certificates | null = null ,
@@ -230,7 +276,7 @@ export class AdaService extends Service {
230276 txBuilder . set_certs ( certificates ) ;
231277 }
232278
233- if ( withdrawals ) {
279+ if ( withdrawals ) {
234280 txBuilder . set_withdrawals ( withdrawals ) ;
235281 }
236282
@@ -239,12 +285,8 @@ export class AdaService extends Service {
239285 ( u : any ) => ! u . amount . find ( ( a : any ) => a . unit !== 'lovelace' ) ,
240286 ) ;
241287
242- const unspentOutputs = TransactionUnspentOutputs . new ( ) ;
243288 for ( const utxo of lovelaceUtxos ) {
244- const amount = utxo . amount . find (
245- ( a : any ) => a . unit === 'lovelace' ,
246- ) ?. quantity ;
247-
289+ const amount = utxo . amount . find ( a => a . unit === 'lovelace' ) ?. quantity ;
248290 if ( ! amount ) continue ;
249291
250292 const inputValue = Value . new (
@@ -255,15 +297,9 @@ export class AdaService extends Service {
255297 TransactionHash . from_bytes ( Buffer . from ( utxo . tx_hash , 'hex' ) ) ,
256298 utxo . output_index ,
257299 ) ;
258- const output = TransactionOutput . new ( Address . from_bech32 ( changeAddress ) , inputValue ) ;
259- unspentOutputs . add ( TransactionUnspentOutput . new ( input , output ) ) ;
300+ txBuilder . add_input ( Address . from_bech32 ( inputAddress ) , input , inputValue ) ;
260301 }
261302
262- txBuilder . add_inputs_from (
263- unspentOutputs ,
264- CoinSelectionStrategyCIP2 . LargestFirst ,
265- ) ;
266-
267303 // Outputs
268304 txBuilder . add_output ( outputs . get ( 0 ) ) ;
269305
@@ -276,9 +312,7 @@ export class AdaService extends Service {
276312 const ttl = currentSlot + 7200 ;
277313 txBuilder . set_ttl ( ttl ) ;
278314
279- // Adds a change output if there are more ADA in utxo than we need for the transaction,
280- // these coins will be returned to change address
281- txBuilder . add_change_if_needed ( Address . from_bech32 ( changeAddress ) ) ;
315+ txBuilder . set_fee ( BigNum . from_str ( CARDANO_PARAMS . DEFAULT_NATIVE_FEES . toString ( ) ) ) ;
282316
283317 return txBuilder . build_tx ( ) ;
284318 }
@@ -306,6 +340,40 @@ export class AdaService extends Service {
306340 return utxo ;
307341 }
308342
343+ /**
344+ * Calculate wallet total balance from given utxo
345+ * @param utxos
346+ * @private
347+ */
348+ private getWalletBalance ( utxos : UTXO ) : number {
349+ let walletBalance = 0 ;
350+ for ( const utxo of utxos ) {
351+ if ( utxo . amount . length > 0 && utxo . amount [ 0 ] . unit === 'lovelace' ) {
352+ walletBalance += Number ( utxo . amount [ 0 ] . quantity ) ;
353+ }
354+ }
355+ return walletBalance ;
356+ }
357+
358+ /**
359+ * Get available rewards for given stake address
360+ * @param stakeAddress
361+ * @private
362+ */
363+ private async getAvailableRewards ( stakeAddress : string ) : Promise < number > {
364+ let availableRewards = 0 ;
365+ const rewardsHistory = await this . client . accountsRewardsAll ( stakeAddress ) ;
366+ for ( const rewards of rewardsHistory ) {
367+ availableRewards += Number ( rewards . amount ) ;
368+ }
369+
370+ const withdrawalsHistory = await this . client . accountsWithdrawalsAll ( stakeAddress ) ;
371+ for ( const withdrawal of withdrawalsHistory ) {
372+ availableRewards -= Number ( withdrawal . amount ) ;
373+ }
374+ return availableRewards ;
375+ }
376+
309377 /**
310378 * Get stake key keyhash
311379 * @param stakeKey
0 commit comments