11import * as CardanoWasm from '@emurgo/cardano-serialization-lib-nodejs' ;
22import { Transaction } from '@emurgo/cardano-serialization-lib-nodejs' ;
3- import { mnemonicToEntropy } from 'bip39' ;
3+ import { mnemonicToEntropy , generateMnemonic } from 'bip39' ;
44import { Service } from "./service" ;
5- import { InternalAdaConfig , UTXO } from "../types/ada" ;
5+ import { AdaStakeOptions , InternalAdaConfig , UTXO } from "../types/ada" ;
66import {
77 BlockFrostAPI ,
88 BlockfrostServerError ,
@@ -24,46 +24,115 @@ export class AdaService extends Service {
2424 }
2525
2626 /**
27- * Craft ada delegate transaction
27+ * Craft ada delegate transaction, all the wallet's balance will be delegated to the pool
2828 * @param accountId id of the kiln account to use for the stake transaction
2929 * @param walletAddress withdrawal creds /!\ losing it => losing the ability to withdraw
30+ * @param options
3031 */
3132 async craftStakeTx (
3233 accountId : string ,
3334 walletAddress : string ,
35+ options ?: AdaStakeOptions ,
3436 ) : Promise < Transaction > {
3537
3638 let utxo : UTXO = [ ] ;
3739 try {
3840 utxo = await this . client . addressesUtxosAll ( walletAddress ) ;
3941 } catch ( error ) {
4042 if ( error instanceof BlockfrostServerError && error . status_code === 404 ) {
41- // Address derived from the seed was not used yet
42- // In this case Blockfrost API will return 404
43- utxo = [ ] ;
43+ throw new Error ( `You should send ADA to ${ walletAddress } to have enough funds to sent a transaction` ) ;
4444 } else {
4545 throw error ;
4646 }
4747 }
4848
49- if ( utxo . length === 0 ) {
50- throw new Error ( `You should send ADA to ${ walletAddress } to have enough funds to sent a transaction` ) ;
51- }
52-
53- // Get current blockchain slot from latest block
5449 const latestBlock = await this . client . blocksLatest ( ) ;
5550 const currentSlot = latestBlock . slot ;
5651 if ( ! currentSlot ) {
5752 throw Error ( 'Failed to fetch slot number' ) ;
5853 }
5954
60- return this . composeTransaction (
61- walletAddress ,
62- 'addr_test1qqh2fphcgd0qsmwsqf4v8v9z2w3cpmzw5y9nx6h8z9v85qj7mjg5eydjgyvn3md3fwlyt2e4veynlwutp7u99m4l6q2sp3rdkv' ,
63- '1000000' ,
64- utxo ,
65- currentSlot ,
55+ const txBuilder = CardanoWasm . TransactionBuilder . new (
56+ CardanoWasm . TransactionBuilderConfigBuilder . new ( )
57+ . fee_algo (
58+ CardanoWasm . LinearFee . new (
59+ CardanoWasm . BigNum . from_str ( '44' ) ,
60+ CardanoWasm . BigNum . from_str ( '155381' ) ,
61+ ) ,
62+ )
63+ . pool_deposit ( CardanoWasm . BigNum . from_str ( '500000000' ) )
64+ . key_deposit ( CardanoWasm . BigNum . from_str ( '2000000' ) )
65+ . coins_per_utxo_word (
66+ CardanoWasm . BigNum . from_str ( CARDANO_PARAMS . COINS_PER_UTXO_WORD ) ,
67+ )
68+ . max_value_size ( CARDANO_PARAMS . MAX_VALUE_SIZE )
69+ . max_tx_size ( CARDANO_PARAMS . MAX_TX_SIZE )
70+ . build ( ) ,
71+ ) ;
72+
73+ const poolHash = this . testnet ? '3496527c1e1b20d56cc9d4e5615c76ba2421ad3f13e2561a1ad4d6c6' : '78da8fa2f5089964963a0ab7ad1402e8c656f203bef622cf9f5ee3c6' ;
74+ const wasmWalletAddress = CardanoWasm . Address . from_bech32 ( walletAddress ) ;
75+ const baseWalletAddress = CardanoWasm . BaseAddress . from_address ( wasmWalletAddress ) ;
76+
77+
78+ // Set TTL to +2h from currentSlot
79+ // If the transaction is not included in a block before that slot it will be cancelled.
80+ const ttl = currentSlot + 7200 ;
81+ txBuilder . set_ttl ( ttl ) ;
82+
83+ // Add delegation
84+ const poolKeyHash = CardanoWasm . Ed25519KeyHash . from_hex ( poolHash ) ;
85+ const stakeCredentials = baseWalletAddress ?. stake_cred ( ) ;
86+ if ( ! stakeCredentials ) {
87+ throw new Error ( 'Could not generate stake credentials' ) ;
88+ }
89+
90+ const certificates = CardanoWasm . Certificates . new ( ) ;
91+ const stakeRegistration = CardanoWasm . StakeRegistration . new ( stakeCredentials ) ;
92+ const stakeDelegation = CardanoWasm . StakeDelegation . new ( stakeCredentials , poolKeyHash ) ;
93+ const stakeRegistrationCertificate = CardanoWasm . Certificate . new_stake_registration ( stakeRegistration ) ;
94+ const delegationCertificate = CardanoWasm . Certificate . new_stake_delegation ( stakeDelegation ) ;
95+ certificates . add ( stakeRegistrationCertificate ) ;
96+ certificates . add ( delegationCertificate ) ;
97+ txBuilder . set_certs ( certificates ) ;
98+
99+ // Filter out multi asset utxo to keep this simple
100+ const lovelaceUtxos = utxo . filter (
101+ ( u : any ) => ! u . amount . find ( ( a : any ) => a . unit !== 'lovelace' ) ,
102+ ) ;
103+
104+ // Create TransactionUnspentOutputs from utxos fetched from Blockfrost
105+ const unspentOutputs = CardanoWasm . TransactionUnspentOutputs . new ( ) ;
106+ for ( const utxo of lovelaceUtxos ) {
107+ const amount = utxo . amount . find (
108+ ( a : any ) => a . unit === 'lovelace' ,
109+ ) ?. quantity ;
110+
111+ if ( ! amount ) continue ;
112+
113+ const inputValue = CardanoWasm . Value . new (
114+ CardanoWasm . BigNum . from_str ( amount . toString ( ) ) ,
115+ ) ;
116+
117+ const input = CardanoWasm . TransactionInput . new (
118+ CardanoWasm . TransactionHash . from_bytes ( Buffer . from ( utxo . tx_hash , 'hex' ) ) ,
119+ utxo . output_index ,
120+ ) ;
121+ const output = CardanoWasm . TransactionOutput . new ( wasmWalletAddress , inputValue ) ;
122+ unspentOutputs . add ( CardanoWasm . TransactionUnspentOutput . new ( input , output ) ) ;
123+ }
124+
125+ txBuilder . add_inputs_from (
126+ unspentOutputs ,
127+ CardanoWasm . CoinSelectionStrategyCIP2 . LargestFirst ,
66128 ) ;
129+
130+ // Adds a change output if there are more ADA in utxo than we need for the transaction,
131+ // these coins will be returned to change address
132+ txBuilder . add_change_if_needed ( wasmWalletAddress ) ;
133+
134+ // Build transaction
135+ return txBuilder . build_tx ( ) ;
67136 }
68137
69138 /**
@@ -86,7 +155,7 @@ export class AdaService extends Service {
86155 throw new InvalidIntegration ( `Could not retrieve fireblocks signer.` ) ;
87156 }
88157
89- const message = transaction . to_hex ( ) ;
158+ const message = CardanoWasm . hash_transaction ( transaction . body ( ) ) . to_hex ( ) ;
90159
91160 const payload = {
92161 rawMessageData : {
@@ -132,90 +201,6 @@ export class AdaService extends Service {
132201 }
133202 }
134203
135- private composeTransaction (
136- address : string ,
137- outputAddress : string ,
138- outputAmount : string ,
139- utxos : UTXO ,
140- currentSlot : number ,
141- ) : Transaction {
142- if ( ! utxos || utxos . length === 0 ) {
143- throw Error ( `No utxo on address ${ address } ` ) ;
144- }
145-
146- const txBuilder = CardanoWasm . TransactionBuilder . new (
147- CardanoWasm . TransactionBuilderConfigBuilder . new ( )
148- . fee_algo (
149- CardanoWasm . LinearFee . new (
150- CardanoWasm . BigNum . from_str ( '44' ) ,
151- CardanoWasm . BigNum . from_str ( '155381' ) ,
152- ) ,
153- )
154- . pool_deposit ( CardanoWasm . BigNum . from_str ( '500000000' ) )
155- . key_deposit ( CardanoWasm . BigNum . from_str ( '2000000' ) )
156- . coins_per_utxo_word (
157- CardanoWasm . BigNum . from_str ( CARDANO_PARAMS . COINS_PER_UTXO_WORD ) ,
158- )
159- . max_value_size ( CARDANO_PARAMS . MAX_VALUE_SIZE )
160- . max_tx_size ( CARDANO_PARAMS . MAX_TX_SIZE )
161- . build ( ) ,
162- ) ;
163-
164- const outputAddr = CardanoWasm . Address . from_bech32 ( outputAddress ) ;
165- const changeAddr = CardanoWasm . Address . from_bech32 ( address ) ;
166-
167- // Set TTL to +2h from currentSlot
168- // If the transaction is not included in a block before that slot it will be cancelled.
169- const ttl = currentSlot + 7200 ;
170- txBuilder . set_ttl ( ttl ) ;
171-
172- // Add output to the tx
173- txBuilder . add_output (
174- CardanoWasm . TransactionOutput . new (
175- outputAddr ,
176- CardanoWasm . Value . new ( CardanoWasm . BigNum . from_str ( outputAmount ) ) ,
177- ) ,
178- ) ;
179-
180- // Filter out multi asset utxo to keep this simple
181- const lovelaceUtxos = utxos . filter (
182- ( u : any ) => ! u . amount . find ( ( a : any ) => a . unit !== 'lovelace' ) ,
183- ) ;
184-
185- // Create TransactionUnspentOutputs from utxos fetched from Blockfrost
186- const unspentOutputs = CardanoWasm . TransactionUnspentOutputs . new ( ) ;
187- for ( const utxo of lovelaceUtxos ) {
188- const amount = utxo . amount . find (
189- ( a : any ) => a . unit === 'lovelace' ,
190- ) ?. quantity ;
191-
192- if ( ! amount ) continue ;
193-
194- const inputValue = CardanoWasm . Value . new (
195- CardanoWasm . BigNum . from_str ( amount . toString ( ) ) ,
196- ) ;
197-
198- const input = CardanoWasm . TransactionInput . new (
199- CardanoWasm . TransactionHash . from_bytes ( Buffer . from ( utxo . tx_hash , 'hex' ) ) ,
200- utxo . output_index ,
201- ) ;
202- const output = CardanoWasm . TransactionOutput . new ( changeAddr , inputValue ) ;
203- unspentOutputs . add ( CardanoWasm . TransactionUnspentOutput . new ( input , output ) ) ;
204- }
205-
206- txBuilder . add_inputs_from (
207- unspentOutputs ,
208- CardanoWasm . CoinSelectionStrategyCIP2 . LargestFirst ,
209- ) ;
210-
211- // Adds a change output if there are more ADA in utxo than we need for the transaction,
212- // these coins will be returned to change address
213- txBuilder . add_change_if_needed ( changeAddr ) ;
214-
215- // Build transaction
216- return txBuilder . build_tx ( ) ;
217- } ;
218-
219204 private harden ( num : number ) : number {
220205 return 0x80000000 + num ;
221206 } ;
0 commit comments