Skip to content

Commit e4ad8be

Browse files
nooxxloicttn
authored andcommitted
wip
1 parent 54ca1de commit e4ad8be

File tree

2 files changed

+147
-69
lines changed

2 files changed

+147
-69
lines changed

examples/ada.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ const f = async () => {
2323
'376acfff-e35d-4b7c-90da-c6acb8ea7197',
2424
'addr_test1qpy358g8glafrucevf0rjpmzx2k5esn5uvjh7dzuakpdhv4g2egyt3y3qw6jrguz0lmyhxygjdg2ytaf5z6ueaety7dsmpcee5',
2525
);
26-
// const txSigned = await k.ada.sign('vault1', tx);
27-
// const hash = await k.ada.broadcast(txSigned);
28-
// console.log(hash);
26+
const txSigned = await k.ada.sign('vault1', tx);
27+
const hash = await k.ada.broadcast(txSigned);
28+
console.log(hash);
2929
} catch (err){
3030
console.log(err);
3131
}

src/services/ada.ts

Lines changed: 144 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
import * as CardanoWasm from '@emurgo/cardano-serialization-lib-nodejs';
2-
import { Transaction } from '@emurgo/cardano-serialization-lib-nodejs';
3-
import { mnemonicToEntropy, generateMnemonic } from 'bip39';
2+
import {
3+
Address,
4+
BigNum,
5+
Certificate,
6+
Certificates,
7+
Ed25519KeyHash,
8+
LinearFee, PublicKey,
9+
RewardAddress,
10+
StakeCredential,
11+
StakeDelegation,
12+
StakeRegistration,
13+
Transaction,
14+
TransactionBuilder,
15+
TransactionBuilderConfigBuilder,
16+
TransactionOutput,
17+
TransactionOutputs,
18+
Value, Vkey, VRFVKey,
19+
} from '@emurgo/cardano-serialization-lib-nodejs';
20+
import { mnemonicToEntropy } from 'bip39';
421
import { Service } from "./service";
522
import { AdaStakeOptions, InternalAdaConfig, UTXO } from "../types/ada";
623
import {
@@ -13,6 +30,10 @@ const CARDANO_PARAMS = {
1330
COINS_PER_UTXO_WORD: '34482',
1431
MAX_TX_SIZE: 16384,
1532
MAX_VALUE_SIZE: 5000,
33+
MIN_FEE_A: '44',
34+
MIN_FEE_B: '155381',
35+
POOL_DEPOSIT: '500000000',
36+
KEY_DEPOSIT: '2000000',
1637
};
1738

1839
export class AdaService extends Service {
@@ -34,41 +55,78 @@ export class AdaService extends Service {
3455
walletAddress: string,
3556
options?: AdaStakeOptions,
3657
): Promise<Transaction> {
58+
const poolId = this.testnet ? 'pool1xjt9ylq7rvsd2mxf6njkzhrkhgjzrtflz039vxs66ntvv82rdky' : 'pool10rdglgh4pzvkf936p2m669qzarr9dusrhmmz9nultm3uvq4eh5k';
3759
const poolHash = this.testnet ? '3496527c1e1b20d56cc9d4e5615c76ba2421ad3f13e2561a1ad4d6c6' : '78da8fa2f5089964963a0ab7ad1402e8c656f203bef622cf9f5ee3c6';
38-
const wasmWalletAddress = CardanoWasm.Address.from_bech32(walletAddress);
39-
const baseWalletAddress = CardanoWasm.BaseAddress.from_address(wasmWalletAddress);
40-
41-
if(!baseWalletAddress){
42-
throw new Error('Could not generate base wallet address');
60+
const poolKeyHash = Ed25519KeyHash.from_hex(poolHash);
4361

44-
}
45-
let utxo: UTXO = [];
4662
try {
47-
utxo = await this.client.addressesUtxosAll(walletAddress);
48-
} catch (error) {
49-
if (error instanceof BlockfrostServerError && error.status_code === 404) {
50-
throw new Error(`You should send ADA to ${walletAddress} to have enough funds to sent a transaction`);
51-
} else {
52-
throw error;
63+
const utxos = await this.getUtxos(walletAddress);
64+
const outputs = this.prepareTx(CARDANO_PARAMS.KEY_DEPOSIT, walletAddress);
65+
const address = await this.client.addresses(walletAddress);
66+
if (!address.stake_address) {
67+
throw Error('No stake address');
68+
}
69+
const stakeKeyHash = await this.getStakeKeyHash(address.stake_address);
70+
if (!stakeKeyHash) {
71+
throw Error('Could not hash stake key');
72+
}
73+
const certificates = Certificates.new();
74+
75+
const registrations = await this.client.accountsRegistrations(address.stake_address);
76+
// const pool = await this.client.poolsById(poolId);
77+
78+
// Register stake key if not done already
79+
if (registrations.length === 0) {
80+
certificates.add(
81+
Certificate.new_stake_registration(
82+
StakeRegistration.new(
83+
StakeCredential.from_keyhash(
84+
Ed25519KeyHash.from_bytes(stakeKeyHash),
85+
),
86+
),
87+
),
88+
);
5389
}
90+
certificates.add(
91+
Certificate.new_stake_delegation(
92+
StakeDelegation.new(
93+
StakeCredential.from_keyhash(
94+
Ed25519KeyHash.from_bytes(stakeKeyHash),
95+
),
96+
poolKeyHash,
97+
),
98+
),
99+
);
100+
return await this.buildTx(walletAddress, utxos, outputs, certificates);
101+
} catch (error) {
102+
throw error;
54103
}
104+
}
55105

56-
const latestBlock = await this.client.blocksLatest();
57-
const currentSlot = latestBlock.slot;
58-
if (!currentSlot) {
59-
throw Error('Failed to fetch slot number');
60-
}
106+
private prepareTx(lovelaceValue: string, paymentAddress: string): TransactionOutputs {
107+
const outputs = TransactionOutputs.new();
61108

62-
const txBuilder = CardanoWasm.TransactionBuilder.new(
63-
CardanoWasm.TransactionBuilderConfigBuilder.new()
109+
outputs.add(
110+
TransactionOutput.new(
111+
Address.from_bech32(paymentAddress),
112+
Value.new(BigNum.from_str(lovelaceValue)),
113+
),
114+
);
115+
116+
return outputs;
117+
}
118+
119+
private async buildTx(changeAddress: string, utxos: UTXO, outputs: TransactionOutputs, certificates: Certificates | null = null) {
120+
const txBuilder = TransactionBuilder.new(
121+
TransactionBuilderConfigBuilder.new()
64122
.fee_algo(
65-
CardanoWasm.LinearFee.new(
66-
CardanoWasm.BigNum.from_str('44'),
67-
CardanoWasm.BigNum.from_str('155381'),
123+
LinearFee.new(
124+
BigNum.from_str(CARDANO_PARAMS.MIN_FEE_A),
125+
BigNum.from_str(CARDANO_PARAMS.MIN_FEE_B),
68126
),
69127
)
70-
.pool_deposit(CardanoWasm.BigNum.from_str('500000000'))
71-
.key_deposit(CardanoWasm.BigNum.from_str('2000000'))
128+
.pool_deposit(CardanoWasm.BigNum.from_str(CARDANO_PARAMS.POOL_DEPOSIT))
129+
.key_deposit(CardanoWasm.BigNum.from_str(CARDANO_PARAMS.KEY_DEPOSIT))
72130
.coins_per_utxo_word(
73131
CardanoWasm.BigNum.from_str(CARDANO_PARAMS.COINS_PER_UTXO_WORD),
74132
)
@@ -77,40 +135,15 @@ export class AdaService extends Service {
77135
.build(),
78136
);
79137

80-
// Set TTL to +2h from currentSlot
81-
// If the transaction is not included in a block before that slot it will be cancelled.
82-
const ttl = currentSlot + 7200;
83-
txBuilder.set_ttl(ttl);
138+
if (certificates) {
139+
txBuilder.set_certs(certificates);
140+
}
84141

85-
// const addresses = await this.client.addresses(walletAddress);
86-
//
87-
// // Add output
88-
// txBuilder.add_output(
89-
// CardanoWasm.TransactionOutput.new(
90-
// wasmWalletAddress,
91-
// CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(addresses.amount[0].quantity.toString())),
92-
// ),
93-
// );
94-
95-
// Add delegation (stake registration + stake delegation certificates)
96-
const poolKeyHash = CardanoWasm.Ed25519KeyHash.from_hex(poolHash);
97-
const stakeCredentials = baseWalletAddress.stake_cred();
98-
console.log(stakeCredentials);
99-
const certificates = CardanoWasm.Certificates.new();
100-
const stakeRegistration = CardanoWasm.StakeRegistration.new(stakeCredentials);
101-
const stakeDelegation = CardanoWasm.StakeDelegation.new(stakeCredentials, poolKeyHash);
102-
const stakeRegistrationCertificate = CardanoWasm.Certificate.new_stake_registration(stakeRegistration);
103-
const delegationCertificate = CardanoWasm.Certificate.new_stake_delegation(stakeDelegation);
104-
certificates.add(stakeRegistrationCertificate);
105-
certificates.add(delegationCertificate);
106-
txBuilder.set_certs(certificates);
107-
108-
// Filter out multi asset utxo to keep this simple
109-
const lovelaceUtxos = utxo.filter(
142+
// Inputs
143+
const lovelaceUtxos = utxos.filter(
110144
(u: any) => !u.amount.find((a: any) => a.unit !== 'lovelace'),
111145
);
112146

113-
// Create TransactionUnspentOutputs from utxos fetched from Blockfrost
114147
const unspentOutputs = CardanoWasm.TransactionUnspentOutputs.new();
115148
for (const utxo of lovelaceUtxos) {
116149
const amount = utxo.amount.find(
@@ -127,7 +160,7 @@ export class AdaService extends Service {
127160
CardanoWasm.TransactionHash.from_bytes(Buffer.from(utxo.tx_hash, 'hex')),
128161
utxo.output_index,
129162
);
130-
const output = CardanoWasm.TransactionOutput.new(wasmWalletAddress, inputValue);
163+
const output = CardanoWasm.TransactionOutput.new(Address.from_bech32(changeAddress), inputValue);
131164
unspentOutputs.add(CardanoWasm.TransactionUnspentOutput.new(input, output));
132165
}
133166

@@ -136,14 +169,59 @@ export class AdaService extends Service {
136169
CardanoWasm.CoinSelectionStrategyCIP2.LargestFirst,
137170
);
138171

172+
// Outputs
173+
txBuilder.add_output(outputs.get(0));
174+
175+
176+
const latestBlock = await this.client.blocksLatest();
177+
const currentSlot = latestBlock.slot;
178+
if (!currentSlot) {
179+
throw Error('Failed to fetch slot number');
180+
}
181+
// Current slot + 2h
182+
const ttl = currentSlot + 7200;
183+
txBuilder.set_ttl(ttl);
184+
139185
// Adds a change output if there are more ADA in utxo than we need for the transaction,
140186
// these coins will be returned to change address
141-
txBuilder.add_change_if_needed(wasmWalletAddress);
187+
txBuilder.add_change_if_needed(Address.from_bech32(changeAddress));
142188

143-
// Build transaction
144189
return txBuilder.build_tx();
145190
}
146191

192+
private adaToLovelace(value: string) {
193+
return (parseFloat(value || '1') * 1000000).toFixed();
194+
}
195+
196+
private hexToBytes(string: string) {
197+
return Buffer.from(string, 'hex');
198+
}
199+
200+
private hexToBech32(address: string) {
201+
return Address.from_bytes(this.hexToBytes(address)).to_bech32();
202+
}
203+
204+
private async getUtxos(walletAddress: string) {
205+
let utxo: UTXO = [];
206+
try {
207+
utxo = await this.client.addressesUtxosAll(walletAddress);
208+
} catch (error) {
209+
if (error instanceof BlockfrostServerError && error.status_code === 404) {
210+
throw new Error(`You should send ADA to ${walletAddress} to have enough funds to sent a transaction`);
211+
} else {
212+
throw error;
213+
}
214+
}
215+
return utxo;
216+
}
217+
218+
private getStakeKeyHash(stakeKey: string) {
219+
const rewardAddress = RewardAddress.from_address(Address.from_bech32(stakeKey));
220+
const paymentCred = rewardAddress?.payment_cred();
221+
const hash = paymentCred?.to_keyhash();
222+
return hash?.to_bytes();
223+
}
224+
147225
/**
148226
* Sign transaction with given integration
149227
* @param integration
@@ -172,11 +250,11 @@ export class AdaService extends Service {
172250
{
173251
"content": message,
174252
},
175-
]
253+
],
176254
},
177255
inputsSelection: {
178256
inputsToSpend: JSON.parse(transaction.body().inputs().to_json()),
179-
}
257+
},
180258
};
181259

182260
const fbTx = await this.fbSigner.signWithFB(payload, this.testnet ? 'ADA_TEST' : 'ADA');
@@ -202,19 +280,19 @@ export class AdaService extends Service {
202280
} catch (error) {
203281
// submit could fail if the transactions is rejected by cardano node
204282
if (error instanceof BlockfrostServerError && error.status_code === 400) {
205-
console.log(error.message);
283+
console.log(error.stack, error.error);
206284
} else {
207285
// rethrow other errors
208286
throw error;
209287
}
210288
}
211289
}
212290

213-
private harden (num: number): number {
291+
private harden(num: number): number {
214292
return 0x80000000 + num;
215293
};
216294

217-
private deriveAddressPrvKey (
295+
private deriveAddressPrvKey(
218296
bipPrvKey: CardanoWasm.Bip32PrivateKey,
219297
testnet: boolean,
220298
): {
@@ -254,7 +332,7 @@ export class AdaService extends Service {
254332
return { signKey: utxoKey.to_raw_key(), address: address };
255333
};
256334

257-
private mnemonicToPrivateKey (
335+
private mnemonicToPrivateKey(
258336
mnemonic: string,
259337
): CardanoWasm.Bip32PrivateKey {
260338
const entropy = mnemonicToEntropy(mnemonic);

0 commit comments

Comments
 (0)