Skip to content

Commit d77e411

Browse files
Merge pull request #6423 from BitGo/WIN-6028
feat(sdk-coin-sui): fee payer signing support
2 parents ca19584 + 96797ea commit d77e411

File tree

4 files changed

+100
-0
lines changed

4 files changed

+100
-0
lines changed

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export abstract class Transaction<T> extends BaseTransaction {
2424
protected _suiTransaction: SuiTransaction<T>;
2525
protected _signature: Signature;
2626
private _serializedSig: Uint8Array;
27+
protected _feePayerSignature: Signature;
28+
private _serializedFeePayerSig: Uint8Array;
2729

2830
protected constructor(_coinConfig: Readonly<CoinConfig>) {
2931
super(_coinConfig);
@@ -51,6 +53,12 @@ export abstract class Transaction<T> extends BaseTransaction {
5153
this.serialize();
5254
}
5355

56+
addFeePayerSignature(publicKey: BasePublicKey, signature: Buffer): void {
57+
this._signatures.push(signature.toString('hex'));
58+
this._feePayerSignature = { publicKey, signature };
59+
this.serialize();
60+
}
61+
5462
get suiSignature(): Signature {
5563
return this._signature;
5664
}
@@ -59,6 +67,10 @@ export abstract class Transaction<T> extends BaseTransaction {
5967
return this._serializedSig;
6068
}
6169

70+
get serializedFeePayerSig(): Uint8Array {
71+
return this._serializedFeePayerSig;
72+
}
73+
6274
setSerializedSig(publicKey: BasePublicKey, signature: Buffer): void {
6375
const pubKey = Buffer.from(publicKey.pub, 'hex');
6476
const serialized_sig = new Uint8Array(1 + signature.length + pubKey.length);
@@ -68,6 +80,15 @@ export abstract class Transaction<T> extends BaseTransaction {
6880
this._serializedSig = serialized_sig;
6981
}
7082

83+
setSerializedFeePayerSig(publicKey: BasePublicKey, signature: Buffer): void {
84+
const pubKey = Buffer.from(publicKey.pub, 'hex');
85+
const serialized_sig = new Uint8Array(1 + signature.length + pubKey.length);
86+
serialized_sig.set(SIGNATURE_SCHEME_BYTES);
87+
serialized_sig.set(signature, 1);
88+
serialized_sig.set(pubKey, 1 + signature.length);
89+
this._serializedFeePayerSig = serialized_sig;
90+
}
91+
7192
/** @inheritdoc */
7293
canSign(key: BaseKey): boolean {
7394
return true;
@@ -91,6 +112,28 @@ export abstract class Transaction<T> extends BaseTransaction {
91112
this.addSignature({ pub: signer.getKeys().pub }, Buffer.from(signature));
92113
}
93114

115+
/**
116+
* Sign transaction as the gas owner (fee payer).
117+
*
118+
* @param {KeyPair} ownerKey - Key for gas owner.
119+
*/
120+
signAsFeePayer(signer: KeyPair): void {
121+
if (!this._suiTransaction) {
122+
throw new InvalidTransactionError('empty transaction to sign');
123+
}
124+
125+
const intentMessage = this.signablePayload;
126+
const signature = signer.signMessageinUint8Array(intentMessage);
127+
128+
this._feePayerSignature = {
129+
publicKey: { pub: signer.getKeys().pub },
130+
signature: Buffer.from(signature),
131+
};
132+
133+
// Set serialized signature for fee payer
134+
this.setSerializedFeePayerSig({ pub: signer.getKeys().pub }, Buffer.from(signature));
135+
}
136+
94137
/** @inheritdoc */
95138
toBroadcastFormat(): string {
96139
if (!this._suiTransaction) {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ export abstract class TransactionBuilder<T = SuiProgrammableTransaction> extends
6363
this.transaction.setSerializedSig(publicKey, signature);
6464
}
6565

66+
addFeePayerSignature(publicKey: BasePublicKey, signature: Buffer): void {
67+
this._signatures.push({ publicKey, signature });
68+
this.transaction.addFeePayerSignature(publicKey, signature);
69+
this.transaction.setSerializedFeePayerSig(publicKey, signature);
70+
}
71+
6672
/**
6773
* Sets the sender of this transaction.
6874
* This account will be responsible for paying transaction fees.

modules/sdk-coin-sui/test/resources/sui.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ export const sender = {
3939
'AETvicGY1HwWjQokRg2HgbQeu+QQZP4ejZQGjmfWvPUd2WkzBudVlaSzjiS1btS2/34Laf6rfkNKYD540crafAxzTVAmV/9J1skZyoX4AWkJM/R4Y1FfV36atFLbCwUVqQ==',
4040
};
4141

42+
export const feePayer = {
43+
address: '0x77291376d885efa752ed921b48d1aa1a65a389bf214ec0eab8b31970c9ab3618',
44+
publicKey: 'TBP5Vc7o+A1dlVd7B/YktCMi2K4frfBqG5FoKAlSRyI=',
45+
signatureHex: '0sQRp5hwfSnjOXx/hofBbzR/AOmDmeIexKYYzqf9T/xPXx8/TTGlvOS6P+fvzQhSPyCi0BV8DKPkgC0DEDqXAg==',
46+
};
47+
4248
export const recipients: Recipient[] = [
4349
{
4450
address: addresses.validAddresses[0],
@@ -326,6 +332,13 @@ export const gasData = {
326332
budget: GAS_BUDGET,
327333
};
328334

335+
export const gasDataHavingDifferentOwner = {
336+
payment: coinsGasPayment,
337+
owner: feePayer.address,
338+
price: DUMMY_SUI_GAS_PRICE,
339+
budget: GAS_BUDGET,
340+
};
341+
329342
export const gasDataWithoutGasPayment = {
330343
owner: sender.address,
331344
price: DUMMY_SUI_GAS_PRICE,
@@ -385,6 +398,9 @@ export const CUSTOM_TX_PUBLIC_TRANSFER =
385398
export const UNSUPPORTED_TX =
386399
'AAADAQB8/cv/rmQ4SGTkEEwSbyfbLPq1wUyg1W6IvFtoo+8KqlC2vgAAAAAAICf6QebgEjDOJoW4sw5jWv5y/UufjHTKEBOHgJuTvJUiAQCxKLKh9y8GFhre5P0lgTbBRPtAyf0t8qUokHbou50E7NKapQAAAAAAIOQxj17At6WByUlaVwW8ARhUgNYg+4piw4zD/Y0NmkGkACD2MpmTSjDA8xA/Swgzt0TpPwDQMzvfr4FvB1MJAXguuAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMMc3Rha2luZ19wb29sEHNwbGl0X3N0YWtlZF9zdWkAAgEAAAEBAAEBAgAAAQIA07HNlidQ0GBG1SOg7xObuweDJ/1vEx0EdAuFFDmMwsEBG9JpdXApX73818xmw/q0XMj4sGCPGfoT7IqE5kfKbrgJAAAAAAAAACBx2foz6S5yGPqcihv4KTDtuVntp4JleEuKIOTdKq6RONOxzZYnUNBgRtUjoO8Tm7sHgyf9bxMdBHQLhRQ5jMLB6AMAAAAAAAAAypo7AAAAAAA=';
387400

401+
export const FEE_SPONSOR_TRANSFER =
402+
'AAAEAAhkAAAAAAAAAAAg+UGuPL5WRdzMFdqDRrUz9/kfICCJpVIWU8Bisv8QswQACGQAAAAAAAAAACB3w7WyESl5PEpWAiIKS5cAB8VNSplt6UHltxNxmkL4/gQCAAEBAAABAQIAAAEBAAIAAQECAAEBAgIAAQMAmIIYi6PoBwqbsGrpRGz2B5FO6O5Y7YMGo+Ov/1obu3ECCcQFIq7VS87PpINgXF2lghsXGsGqG2FZcfuN/iftE/1RBAAAAAAAACC2RGfJXC7cVwfXSDKdKwQB/rPC0/3tdlzDSomluG57sifdAOf8zch7TZW2OEtzkRm5HyqBoWuu3qf04AaOUpQ32QAAAAAAAAAgvik+0ypZjmC8kkbE4BtuQpN/FomQiDpqIFB6wsFNJyd3KRN22IXvp1LtkhtI0aoaZaOJvyFOwOq4sxlwyas2GOgDAAAAAAAAAC0xAQAAAAAA';
403+
388404
export const invalidRecipients: Recipient[] = [
389405
{
390406
address: addresses.invalidAddresses[0],

modules/sdk-coin-sui/test/unit/transactionBuilder/transactionBuilder.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,41 @@ describe('Sui Transaction Builder', async () => {
6363
reserialized.toBroadcastFormat().should.equal(rawSignedTx);
6464
});
6565

66+
it('should build and sign a transfer tx with gasPayment as different owner', async function () {
67+
const txBuilder = factory.getTransferBuilder();
68+
txBuilder.type(SuiTransactionType.Transfer);
69+
txBuilder.sender(testData.sender.address);
70+
txBuilder.send(recipients);
71+
txBuilder.gasData(testData.gasDataHavingDifferentOwner);
72+
const tx = await txBuilder.build();
73+
should.equal(tx.id, 'UNAVAILABLE');
74+
const rawTx = tx.toBroadcastFormat();
75+
should.equal(rawTx, testData.FEE_SPONSOR_TRANSFER);
76+
77+
const txBuilder2 = await factory.from(rawTx);
78+
await txBuilder2.addSignature({ pub: testData.sender.publicKey }, Buffer.from(testData.sender.signatureHex));
79+
await txBuilder2.addFeePayerSignature(
80+
{ pub: testData.feePayer.publicKey },
81+
Buffer.from(testData.feePayer.signatureHex)
82+
);
83+
const signedTx = await txBuilder2.build();
84+
should.equal(signedTx.type, TransactionType.Send);
85+
should.equal(signedTx.id, 'Co5d6tiLQuwUPrfPp7vfmcJMNK5gWZ3StFR6cvzq6R12');
86+
87+
const rawSignedTx = signedTx.toBroadcastFormat();
88+
should.equal(rawSignedTx, testData.FEE_SPONSOR_TRANSFER);
89+
const reserializedTxBuilder = factory.from(rawSignedTx);
90+
reserializedTxBuilder.addSignature({ pub: testData.sender.publicKey }, Buffer.from(testData.sender.signatureHex));
91+
reserializedTxBuilder.addFeePayerSignature(
92+
{ pub: testData.feePayer.publicKey },
93+
Buffer.from(testData.feePayer.signatureHex)
94+
);
95+
const reserialized = await reserializedTxBuilder.build();
96+
97+
reserialized.should.be.deepEqual(signedTx);
98+
reserialized.toBroadcastFormat().should.equal(rawSignedTx);
99+
});
100+
66101
it('should submit a transfer transaction with private keys', async () => {
67102
const keyPairSender = new KeyPair({ prv: testData.privateKeys.prvKey1 });
68103
const keyPairRecipient = new KeyPair({ prv: testData.privateKeys.prvKey2 });

0 commit comments

Comments
 (0)