Skip to content

Commit df7b341

Browse files
Merge pull request #5353 from BitGo/manas/WIN-4056-byron-support-for-ADA
feat: Byron era support for ADA wallets
2 parents 596f6e0 + 3773ebf commit df7b341

File tree

6 files changed

+217
-30
lines changed

6 files changed

+217
-30
lines changed

modules/sdk-coin-ada/src/lib/transaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export class Transaction extends BaseTransaction {
148148
for (let i = 0; i < this._transaction.body().outputs().len(); i++) {
149149
const output = this._transaction.body().outputs().get(i);
150150
result.outputs.push({
151-
address: output.address().to_bech32(),
151+
address: adaUtils.getAddressString(output.address()),
152152
amount: output.amount().coin().to_str(),
153153
multiAssets: output.amount().multiasset() || undefined,
154154
});
@@ -237,7 +237,7 @@ export class Transaction extends BaseTransaction {
237237
for (let i = 0; i < tx_outputs.len(); i++) {
238238
const output = tx_outputs.get(i);
239239
outputs.push({
240-
address: output.address().to_bech32(),
240+
address: adaUtils.getAddressString(output.address()),
241241
value: output.amount().coin().to_str(),
242242
});
243243
}

modules/sdk-coin-ada/src/lib/transactionBuilder.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
148148
this._transactionOutputs.forEach((output) => {
149149
const amount = CardanoWasm.BigNum.from_str(output.amount);
150150
outputs.add(
151-
CardanoWasm.TransactionOutput.new(
152-
CardanoWasm.Address.from_bech32(output.address),
153-
CardanoWasm.Value.new(amount)
154-
)
151+
CardanoWasm.TransactionOutput.new(util.getWalletAddress(output.address), CardanoWasm.Value.new(amount))
155152
);
156153
totalAmountToSend = totalAmountToSend.checked_add(amount);
157154
});
@@ -160,7 +157,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
160157
// estimate fee
161158
// add extra output for the change
162159
if (this._changeAddress && this._senderBalance) {
163-
const changeAddress = CardanoWasm.Address.from_bech32(this._changeAddress);
160+
const changeAddress = util.getWalletAddress(this._changeAddress);
164161
const utxoBalance = CardanoWasm.BigNum.from_str(this._senderBalance);
165162

166163
const adjustment = BigNum.from_str('2000000');
@@ -188,7 +185,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
188185
this._multiAssets.forEach((asset) => {
189186
let txOutputBuilder = CardanoWasm.TransactionOutputBuilder.new();
190187
// changeAddress is the root address, which is where we want the tokens assets to be sent to
191-
const toAddress = CardanoWasm.Address.from_bech32(this._changeAddress);
188+
const toAddress = util.getWalletAddress(this._changeAddress);
192189
txOutputBuilder = txOutputBuilder.with_address(toAddress);
193190
let txOutputAmountBuilder = txOutputBuilder.next();
194191
const assetName = CardanoWasm.AssetName.new(Buffer.from(asset.asset_name, 'hex'));
@@ -301,7 +298,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
301298
const quantity = assets!.get(assetName);
302299
let txOutputBuilder = CardanoWasm.TransactionOutputBuilder.new();
303300
const outputAmount = CardanoWasm.BigNum.from_str(output.amount);
304-
const toAddress = CardanoWasm.Address.from_bech32(output.address);
301+
const toAddress = util.getWalletAddress(output.address);
305302
txOutputBuilder = txOutputBuilder.with_address(toAddress);
306303
let txOutputAmountBuilder = txOutputBuilder.next();
307304
const multiAsset = CardanoWasm.MultiAsset.new();
@@ -314,14 +311,14 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
314311
} else {
315312
outputs.add(
316313
CardanoWasm.TransactionOutput.new(
317-
CardanoWasm.Address.from_bech32(output.address),
314+
util.getWalletAddress(output.address),
318315
CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(output.amount))
319316
)
320317
);
321318
}
322319
});
323320
if (this._changeAddress && this._senderBalance) {
324-
const changeAddress = CardanoWasm.Address.from_bech32(this._changeAddress);
321+
const changeAddress = util.getWalletAddress(this._changeAddress);
325322
const utxoBalance = CardanoWasm.BigNum.from_str(this._senderBalance);
326323

327324
const adjustment = BigNum.from_str('2000000');
@@ -348,7 +345,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
348345
this._multiAssets.forEach((asset) => {
349346
let txOutputBuilder = CardanoWasm.TransactionOutputBuilder.new();
350347
// changeAddress is the root address, which is where we want the tokens assets to be sent to
351-
const toAddress = CardanoWasm.Address.from_bech32(this._changeAddress);
348+
const toAddress = util.getWalletAddress(this._changeAddress);
352349
txOutputBuilder = txOutputBuilder.with_address(toAddress);
353350
let txOutputAmountBuilder = txOutputBuilder.next();
354351
const assetName = CardanoWasm.AssetName.new(Buffer.from(asset.asset_name, 'hex'));

modules/sdk-coin-ada/src/lib/utils.ts

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AddressFormat, BaseUtils } from '@bitgo/sdk-core';
1+
import { AddressFormat, BaseUtils, InvalidAddressError } from '@bitgo/sdk-core';
22
import {
33
BaseAddress,
44
PublicKey,
@@ -11,6 +11,10 @@ import {
1111
Ed25519KeyHash,
1212
ScriptHash,
1313
DRepKind,
14+
ByronAddress,
15+
Address,
16+
EnterpriseAddress,
17+
PointerAddress,
1418
} from '@emurgo/cardano-serialization-lib-nodejs';
1519
import { KeyPair } from './keyPair';
1620
import { bech32 } from 'bech32';
@@ -149,26 +153,38 @@ export class Utils implements BaseUtils {
149153
const POINTER_ADDR_LEN = 52;
150154
const VALIDATOR_ADDR_LEN = 56;
151155

152-
// test if this is a bech32 address first
153-
if (new RegExp(bech32PrefixList.join('|')).test(address)) {
156+
//Check for Shelley-era (Bech32) addresses
157+
if (new RegExp(`^(${bech32PrefixList.join('|')})`).test(address)) {
154158
try {
155159
const decodedBech = bech32.decode(address, 108);
156160
const wordLength = decodedBech.words.length;
157-
if (!bech32PrefixList.includes(decodedBech.prefix)) {
158-
return false;
161+
if (
162+
bech32PrefixList.includes(decodedBech.prefix) &&
163+
(wordLength === BASE_ADDR_LEN ||
164+
wordLength === REWARD_AND_ENTERPRISE_ADDR_LEN ||
165+
wordLength === POINTER_ADDR_LEN)
166+
) {
167+
return true;
159168
}
160-
return (
161-
wordLength === BASE_ADDR_LEN ||
162-
wordLength === REWARD_AND_ENTERPRISE_ADDR_LEN ||
163-
wordLength === POINTER_ADDR_LEN
164-
);
165-
} catch (err) {
166-
return false;
169+
} catch (e) {
170+
console.log(`Address: ${address} failed Bech32 test with error: ${e}`);
167171
}
168-
} else {
169-
// maybe this is a validator address
170-
return new RegExp(`^(?!pool)[a-z0-9]\{${VALIDATOR_ADDR_LEN}\}$`).test(address);
171172
}
173+
174+
//Check for Validator addresses
175+
if (new RegExp(`^(?!pool)[a-z0-9]{${VALIDATOR_ADDR_LEN}}$`).test(address)) {
176+
return true;
177+
}
178+
179+
//Check for Byron-era address
180+
try {
181+
return ByronAddress.is_valid(address);
182+
} catch (e) {
183+
console.log(`Address: ${address} failed Byron test with error: ${e}`);
184+
console.log(e.stack);
185+
}
186+
187+
return false;
172188
}
173189

174190
/** @inheritdoc */
@@ -228,6 +244,62 @@ export class Utils implements BaseUtils {
228244
: Buffer.from(serializedTx, 'base64');
229245
return Buffer.from(CardanoTransaction.from_bytes(bufferRawTransaction).body().to_bytes()).toString('hex');
230246
}
247+
248+
/**
249+
* Decode wallet address from string.
250+
* Attempts to decode as Shelley (bech32) first, then Byron (base58).
251+
* @param {string} address - Valid Byron or Shelley-era address.
252+
* @returns {Address} - Valid address object.
253+
* @throws {InvalidAddressError} If the address is neither valid Shelley nor Byron.
254+
*/
255+
getWalletAddress(address: string): Address {
256+
if (!address || typeof address !== 'string') {
257+
throw new InvalidAddressError('Provided address is not a valid string');
258+
}
259+
260+
// Try decoding as a Shelley (bech32) address first
261+
try {
262+
return Address.from_bech32(address);
263+
} catch (e) {
264+
console.error(`Could not decode shelly address from string '${address}'`);
265+
}
266+
267+
// Try decoding as a Byron (base58) address later
268+
try {
269+
return ByronAddress.from_base58(address).to_address();
270+
} catch (e) {
271+
console.error(`Could not decode byron address from string '${address}'`);
272+
}
273+
throw new InvalidAddressError('Provided string is not a valid Shelley or Byron address');
274+
}
275+
276+
/**
277+
* Decode address string from Address object.
278+
* Attempts to decode as Shelley (bech32) first, then Byron (base58).
279+
* @param {Address} address - Valid Address object
280+
* @returns {string} - Valid Byron or Shelley-era address string.
281+
* @throws {InvalidAddressError} If the Address object is neither valid Shelley nor Byron.
282+
*/
283+
getAddressString(address: Address): string {
284+
// Check all Shelley address types
285+
if (
286+
BaseAddress.from_address(address) ||
287+
EnterpriseAddress.from_address(address) ||
288+
RewardAddress.from_address(address) ||
289+
PointerAddress.from_address(address)
290+
) {
291+
return address.to_bech32();
292+
}
293+
294+
// If not Shelley, try Byron
295+
const byronAddress = ByronAddress.from_address(address);
296+
if (byronAddress) {
297+
return byronAddress.to_base58();
298+
}
299+
300+
// If neither, it's invalid
301+
throw new InvalidAddressError('Provided Address is not a valid Shelley or Byron address');
302+
}
231303
}
232304

233305
const utils = new Utils();

0 commit comments

Comments
 (0)