Skip to content

Commit 3773ebf

Browse files
feat: util and txBuilder changes for byron address support
TICKET: WIN-4056
1 parent ec9cb74 commit 3773ebf

File tree

7 files changed

+195
-29
lines changed

7 files changed

+195
-29
lines changed

modules/sdk-coin-ada/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@
4747
"@emurgo/cardano-serialization-lib-nodejs": "^12.0.1",
4848
"bech32": "^2.0.0",
4949
"bignumber.js": "^9.0.2",
50-
"bs58": "^4.0.1",
51-
"cbor": "^9.0.1",
5250
"lodash": "^4.17.21",
5351
"superagent": "^9.0.1",
5452
"tweetnacl": "^1.0.3"

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: 65 additions & 12 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,11 +11,13 @@ 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';
17-
import base58 from 'bs58';
18-
import cbor from 'cbor';
1921

2022
export const MIN_ADA_FOR_ONE_ASSET = '1500000';
2123
export const VOTE_ALWAYS_ABSTAIN = 'always-abstain';
@@ -174,17 +176,12 @@ export class Utils implements BaseUtils {
174176
return true;
175177
}
176178

177-
//Check for Byron-era (Base58 + CBOR)
179+
//Check for Byron-era address
178180
try {
179-
const decodedBase58 = base58.decode(address);
180-
181-
const cborData = cbor.decodeFirstSync(decodedBase58);
182-
183-
if (Array.isArray(cborData) && cborData.length >= 3) {
184-
return true;
185-
}
181+
return ByronAddress.is_valid(address);
186182
} catch (e) {
187-
console.log(`Address: ${address} failed Base58 + CBOR test with error: ${e}`);
183+
console.log(`Address: ${address} failed Byron test with error: ${e}`);
184+
console.log(e.stack);
188185
}
189186

190187
return false;
@@ -247,6 +244,62 @@ export class Utils implements BaseUtils {
247244
: Buffer.from(serializedTx, 'base64');
248245
return Buffer.from(CardanoTransaction.from_bytes(bufferRawTransaction).body().to_bytes()).toString('hex');
249246
}
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+
}
250303
}
251304

252305
const utils = new Utils();

0 commit comments

Comments
 (0)