Skip to content

Commit 74744c2

Browse files
authored
Merge pull request #7950 from BitGo/WIN-8679-support-ada-sponsored-tx
feat(sdk-coin-ada): support for sponsored txns
2 parents da70144 + cb75eda commit 74744c2

2 files changed

Lines changed: 57 additions & 24 deletions

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,8 @@ export class Transaction extends BaseTransaction {
446446
this._fee = fee;
447447
}
448448
}
449+
450+
export interface SponsorshipInfo {
451+
feeAddress: string;
452+
feeAddressInputBalance: string;
453+
}

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

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
TransactionType,
1111
UtilsError,
1212
} from '@bitgo/sdk-core';
13-
import { Asset, Transaction, TransactionInput, TransactionOutput, Withdrawal } from './transaction';
13+
import { Asset, Transaction, TransactionInput, TransactionOutput, Withdrawal, SponsorshipInfo } from './transaction';
1414
import { KeyPair } from './keyPair';
1515
import util, { MIN_ADA_FOR_ONE_ASSET } from './utils';
1616
import * as CardanoWasm from '@emurgo/cardano-serialization-lib-nodejs';
@@ -46,6 +46,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
4646
protected _recipientAddress: string;
4747
/** Map of sender's assets by asset name */
4848
protected _senderAssetList: Record<string, any> = {};
49+
protected _sponsorshipInfo: SponsorshipInfo | undefined; // Enriched for sponsored transactions
4950
private _fee: BigNum;
5051
/** Flag indicating if this is a token transaction */
5152
private _isTokenTransaction = false;
@@ -113,6 +114,11 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
113114
return this;
114115
}
115116

117+
sponsorshipInfo(info: SponsorshipInfo): this {
118+
this._sponsorshipInfo = info;
119+
return this;
120+
}
121+
116122
/**
117123
* Initialize the transaction builder fields using the decoded transaction data
118124
*
@@ -219,10 +225,19 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
219225
* @param outputs - The outputs collection to add to
220226
*/
221227
private addOutputs(outputs) {
222-
const utxoBalance = CardanoWasm.BigNum.from_str(this._senderBalance); // Total UTXO balance
223-
const change = utxoBalance.checked_sub(this._fee);
224-
const changeAfterReceiverDeductions = this.addReceiverOutputs(outputs, change);
225-
this.addChangeOutput(changeAfterReceiverDeductions, outputs);
228+
if (this._sponsorshipInfo) {
229+
const feeAddressUtxoBalance = CardanoWasm.BigNum.from_str(this._sponsorshipInfo.feeAddressInputBalance);
230+
const feeAddressChange = feeAddressUtxoBalance.checked_sub(this._fee);
231+
const senderAddressUtxoBalance = CardanoWasm.BigNum.from_str(this._senderBalance);
232+
const senderChangeAfterReceiverDeductions = this.addReceiverOutputs(outputs, senderAddressUtxoBalance);
233+
this.addChangeOutput(senderChangeAfterReceiverDeductions, outputs, this._changeAddress); // Change address is the sender address
234+
this.addChangeOutput(feeAddressChange, outputs, this._sponsorshipInfo.feeAddress);
235+
} else {
236+
const utxoBalance = CardanoWasm.BigNum.from_str(this._senderBalance); // Total UTXO balance
237+
const change = utxoBalance.checked_sub(this._fee);
238+
const changeAfterReceiverDeductions = this.addReceiverOutputs(outputs, change);
239+
this.addChangeOutput(changeAfterReceiverDeductions, outputs, this._changeAddress);
240+
}
226241
}
227242

228243
/**
@@ -250,6 +265,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
250265
);
251266
}
252267

268+
// output.multiAssets is set when there are token assets to send
253269
const multiAssets = output.multiAssets as Asset;
254270
if (multiAssets) {
255271
const policyId = multiAssets.policy_id;
@@ -273,6 +289,12 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
273289
});
274290

275291
change = change.checked_sub(minAmountNeededForAssetOutput);
292+
} else {
293+
// Native coin send
294+
const amount = CardanoWasm.BigNum.from_str(receiverAmount);
295+
outputs.add(
296+
CardanoWasm.TransactionOutput.new(util.getWalletAddress(receiverAddress), CardanoWasm.Value.new(amount))
297+
);
276298
}
277299
} catch (e) {
278300
if (e instanceof BuildTransactionError) {
@@ -337,26 +359,32 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
337359
* @param change - The change amount in Lovelace
338360
* @param outputs - The outputs collection to add to
339361
*/
340-
private addChangeOutput(change, outputs) {
341-
const changeAddress = util.getWalletAddress(this._changeAddress);
342-
Object.keys(this._mutableSenderAssetList).forEach((fingerprint) => {
343-
const asset = this._mutableSenderAssetList[fingerprint];
344-
const changeQty = asset.quantity;
345-
const policyId = asset.policy_id;
346-
const assetName = asset.asset_name;
347-
348-
if (CardanoWasm.BigNum.from_str(changeQty).is_zero()) {
349-
return;
350-
}
362+
private addChangeOutput(change, outputs, outputAddress) {
363+
const changeAddress = util.getWalletAddress(outputAddress);
364+
/**
365+
* this.mutableSenderAssetList is the list of assets from the sender address
366+
* As ada only utxos are picked for fee address, the assets by default are sent to the sender address
367+
*/
368+
if (!this._sponsorshipInfo || this._sponsorshipInfo.feeAddress !== outputAddress) {
369+
Object.keys(this._mutableSenderAssetList).forEach((fingerprint) => {
370+
const asset = this._mutableSenderAssetList[fingerprint];
371+
const changeQty = asset.quantity;
372+
const policyId = asset.policy_id;
373+
const assetName = asset.asset_name;
374+
375+
if (CardanoWasm.BigNum.from_str(changeQty).is_zero()) {
376+
return;
377+
}
351378

352-
const minAmountNeededForAssetOutput = this.addTokensToOutput(change, outputs, this._changeAddress, {
353-
policy_id: policyId,
354-
asset_name: assetName,
355-
quantity: changeQty,
356-
fingerprint,
379+
const minAmountNeededForAssetOutput = this.addTokensToOutput(change, outputs, outputAddress, {
380+
policy_id: policyId,
381+
asset_name: assetName,
382+
quantity: changeQty,
383+
fingerprint,
384+
});
385+
change = change.checked_sub(minAmountNeededForAssetOutput);
357386
});
358-
change = change.checked_sub(minAmountNeededForAssetOutput);
359-
});
387+
}
360388
if (!change.is_zero()) {
361389
const changeOutput = CardanoWasm.TransactionOutput.new(changeAddress, CardanoWasm.Value.new(change));
362390
outputs.add(changeOutput);
@@ -436,7 +464,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
436464

437465
/** @inheritdoc */
438466
protected async buildImplementation(): Promise<Transaction> {
439-
if (this._isTokenTransaction) {
467+
if (this._isTokenTransaction || (this._sponsorshipInfo && this._type === TransactionType.Send)) {
440468
return this.processTokenBuild();
441469
}
442470
const inputs = CardanoWasm.TransactionInputs.new();

0 commit comments

Comments
 (0)