Skip to content

Commit 1df2ca7

Browse files
committed
Merge branch 'master' into COIN-7315-kava-sdk-support
TICKET: COIN-7315
2 parents e0a31d9 + 468a887 commit 1df2ca7

File tree

33 files changed

+2213
-66
lines changed

33 files changed

+2213
-66
lines changed

.iyarc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
GHSA-8qq5-rm4j-mr97
77

88
# Excluded because:
9-
# - Same tar v6 dependency issue as above
10-
# - Affects only dev dependencies (lerna, yeoman-generator)
11-
# - Not exploitable in our build context
9+
# - Transitive dependency through lerna and yeoman-generator, which currently pin tar to a
10+
# < 7.5.4 range; We only use their tar integration for
11+
# archive PACKING, not extraction,
1212
GHSA-r6q2-hw4h-h46w
1313

Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# An elaborated scheme to build all the dependencies of all packages first in a cached layer
33
# https://stackoverflow.com/a/63142468/134409
44
# https://medium.com/@emilefugulin/building-a-sane-docker-image-for-typescript-lerna-and-prisma-2-76d8ff9926e4
5-
FROM node:22.16.0-bookworm-slim@sha256:2f3571619daafc6b53232ebf2fcc0817c1e64795e92de317c1684a915d13f1a5 AS filter-packages-json
5+
FROM node:22.22.0-bookworm-slim@sha256:f86be15afa9a8277608e141ce2a8aa55d3d9c40845921b8511f4fb7897be2554 AS filter-packages-json
66
LABEL maintainer="Developer Relations <developer-relations-team@bitgo.com>"
77

88
COPY package.json yarn.lock lerna.json ./
@@ -12,7 +12,7 @@ COPY modules ./modules
1212
# delete all the non package.json files under `./modules/`
1313
RUN find modules \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf
1414

15-
FROM node:22.16.0-bookworm-slim@sha256:2f3571619daafc6b53232ebf2fcc0817c1e64795e92de317c1684a915d13f1a5 AS builder
15+
FROM node:22.22.0-bookworm-slim@sha256:f86be15afa9a8277608e141ce2a8aa55d3d9c40845921b8511f4fb7897be2554 AS builder
1616
RUN apt-get update && apt-get install -y git python3 make g++ libtool autoconf automake
1717
WORKDIR /tmp/bitgo
1818
COPY --from=filter-packages-json /tmp/bitgo .
@@ -31,7 +31,7 @@ RUN \
3131
rm -r modules/*/src
3232

3333

34-
FROM node:22.16.0-bookworm-slim@sha256:2f3571619daafc6b53232ebf2fcc0817c1e64795e92de317c1684a915d13f1a5
34+
FROM node:22.22.0-bookworm-slim@sha256:f86be15afa9a8277608e141ce2a8aa55d3d9c40845921b8511f4fb7897be2554
3535
RUN apt-get update && apt-get install -y tini
3636
# copy the root node_modules to the bitgo-express parent node_modules
3737
COPY --from=builder /tmp/bitgo/node_modules /var/node_modules/

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: 59 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,26 @@ 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+
let feeAddressChange = feeAddressUtxoBalance.checked_sub(this._fee);
231+
const senderAddressUtxoBalance = CardanoWasm.BigNum.from_str(this._senderBalance);
232+
if (this._isTokenTransaction) {
233+
// Fee address sponsors min ada
234+
feeAddressChange = this.addReceiverOutputs(outputs, feeAddressChange);
235+
this.addChangeOutput(senderAddressUtxoBalance, outputs, this._changeAddress);
236+
this.addChangeOutput(feeAddressChange, outputs, this._sponsorshipInfo.feeAddress);
237+
} else {
238+
const senderChangeAfterReceiverDeductions = this.addReceiverOutputs(outputs, senderAddressUtxoBalance);
239+
this.addChangeOutput(senderChangeAfterReceiverDeductions, outputs, this._changeAddress);
240+
this.addChangeOutput(feeAddressChange, outputs, this._sponsorshipInfo.feeAddress);
241+
}
242+
} else {
243+
const utxoBalance = CardanoWasm.BigNum.from_str(this._senderBalance); // Total UTXO balance
244+
const change = utxoBalance.checked_sub(this._fee);
245+
const changeAfterReceiverDeductions = this.addReceiverOutputs(outputs, change);
246+
this.addChangeOutput(changeAfterReceiverDeductions, outputs, this._changeAddress);
247+
}
226248
}
227249

228250
/**
@@ -250,6 +272,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
250272
);
251273
}
252274

275+
// output.multiAssets is set when there are token assets to send
253276
const multiAssets = output.multiAssets as Asset;
254277
if (multiAssets) {
255278
const policyId = multiAssets.policy_id;
@@ -273,6 +296,12 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
273296
});
274297

275298
change = change.checked_sub(minAmountNeededForAssetOutput);
299+
} else {
300+
// Native coin send
301+
const amount = CardanoWasm.BigNum.from_str(receiverAmount);
302+
outputs.add(
303+
CardanoWasm.TransactionOutput.new(util.getWalletAddress(receiverAddress), CardanoWasm.Value.new(amount))
304+
);
276305
}
277306
} catch (e) {
278307
if (e instanceof BuildTransactionError) {
@@ -337,26 +366,32 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
337366
* @param change - The change amount in Lovelace
338367
* @param outputs - The outputs collection to add to
339368
*/
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-
}
369+
private addChangeOutput(change, outputs, outputAddress) {
370+
const changeAddress = util.getWalletAddress(outputAddress);
371+
/**
372+
* this.mutableSenderAssetList is the list of assets from the sender address
373+
* As ada only utxos are picked for fee address, the assets by default are sent to the sender address
374+
*/
375+
if (!this._sponsorshipInfo || this._sponsorshipInfo.feeAddress !== outputAddress) {
376+
Object.keys(this._mutableSenderAssetList).forEach((fingerprint) => {
377+
const asset = this._mutableSenderAssetList[fingerprint];
378+
const changeQty = asset.quantity;
379+
const policyId = asset.policy_id;
380+
const assetName = asset.asset_name;
381+
382+
if (CardanoWasm.BigNum.from_str(changeQty).is_zero()) {
383+
return;
384+
}
351385

352-
const minAmountNeededForAssetOutput = this.addTokensToOutput(change, outputs, this._changeAddress, {
353-
policy_id: policyId,
354-
asset_name: assetName,
355-
quantity: changeQty,
356-
fingerprint,
386+
const minAmountNeededForAssetOutput = this.addTokensToOutput(change, outputs, outputAddress, {
387+
policy_id: policyId,
388+
asset_name: assetName,
389+
quantity: changeQty,
390+
fingerprint,
391+
});
392+
change = change.checked_sub(minAmountNeededForAssetOutput);
357393
});
358-
change = change.checked_sub(minAmountNeededForAssetOutput);
359-
});
394+
}
360395
if (!change.is_zero()) {
361396
const changeOutput = CardanoWasm.TransactionOutput.new(changeAddress, CardanoWasm.Value.new(change));
362397
outputs.add(changeOutput);
@@ -436,7 +471,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
436471

437472
/** @inheritdoc */
438473
protected async buildImplementation(): Promise<Transaction> {
439-
if (this._isTokenTransaction) {
474+
if (this._isTokenTransaction || (this._sponsorshipInfo && this._type === TransactionType.Send)) {
440475
return this.processTokenBuild();
441476
}
442477
const inputs = CardanoWasm.TransactionInputs.new();

modules/sdk-coin-ada/test/unit/tokenWithdrawal.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,4 +326,76 @@ describe('ADA Token Operations', async () => {
326326

327327
await txBuilder.build().should.not.be.rejected();
328328
});
329+
330+
it(`should build a sponsored token transaction where fee address sponsors min ADA for receiver`, async () => {
331+
const feeAddress =
332+
'addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp';
333+
const quantity = '20';
334+
const senderInputBalance = 5000000;
335+
const feeAddressInputBalance = 20000000; // Fee address has enough ADA to sponsor
336+
const totalAssetList = {
337+
[fingerprint]: {
338+
quantity: '100',
339+
policy_id: policyId,
340+
asset_name: asciiEncodedName,
341+
},
342+
};
343+
344+
const txBuilder = factory.getTransferBuilder();
345+
// Sender input (has tokens)
346+
txBuilder.input({
347+
transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba21',
348+
transaction_index: 1,
349+
});
350+
// Fee address input (sponsors fees and min ADA)
351+
txBuilder.input({
352+
transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba22',
353+
transaction_index: 0,
354+
});
355+
356+
txBuilder.output({
357+
address: receiverAddress,
358+
amount: '0', // Set ADA amount to 0 for token transfer (min ADA is handled by fee address)
359+
multiAssets: {
360+
asset_name: asciiEncodedName,
361+
policy_id: policyId,
362+
quantity,
363+
fingerprint,
364+
},
365+
});
366+
367+
txBuilder.changeAddress(senderAddress, senderInputBalance.toString(), totalAssetList);
368+
txBuilder.sponsorshipInfo({
369+
feeAddress: feeAddress,
370+
feeAddressInputBalance: feeAddressInputBalance.toString(),
371+
});
372+
txBuilder.ttl(800000000);
373+
txBuilder.isTokenTransaction();
374+
const tx = (await txBuilder.build()) as Transaction;
375+
376+
should.equal(tx.type, TransactionType.Send);
377+
const txData = tx.toJson();
378+
379+
txData.inputs.length.should.equal(2);
380+
txData.outputs.length.should.equal(4);
381+
382+
// Validate receiver output - min ADA should be sponsored by fee address
383+
const receiverOutput = txData.outputs.filter((output) => output.address === receiverAddress);
384+
receiverOutput.length.should.equal(1);
385+
receiverOutput[0].amount.should.equal('1500000'); // Minimum ADA for asset output (sponsored by fee address)
386+
(receiverOutput[0].multiAssets! as CardanoWasm.MultiAsset)
387+
.get_asset(policyScriptHash, CardanoWasm.AssetName.new(Buffer.from(asciiEncodedName, 'hex')))
388+
.to_str()
389+
.should.equal(quantity);
390+
391+
// Validate sender change output - should have full sender balance (tokens only, no ADA deduction for receiver)
392+
const senderChangeOutput = txData.outputs.filter((output) => output.address === senderAddress);
393+
senderChangeOutput.length.should.be.equal(2);
394+
395+
// Validate fee address change output - should have remaining ADA after fees and min ADA for receiver
396+
const feeAddressChangeOutput = txData.outputs.filter((output) => output.address === feeAddress);
397+
feeAddressChangeOutput.length.should.equal(1);
398+
// Fee address change should not have any tokens
399+
should.not.exist(feeAddressChangeOutput[0].multiAssets);
400+
});
329401
});
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* Kava test data for the sdk-coin-cosmos module
3+
* This file extends the base configuration with kava-specific data
4+
*/
5+
6+
import { generateCoinData } from '../testUtils';
7+
8+
export const chainConfig = {
9+
mainnetName: 'Kava',
10+
mainnetCoin: 'kava',
11+
testnetName: 'Testnet Kava',
12+
testnetCoin: 'tkava',
13+
family: 'kava',
14+
decimalPlaces: 6,
15+
baseDenom: 'ukava',
16+
chainId: 'kava_2221-16000',
17+
addressPrefix: 'kava',
18+
validatorPrefix: 'kavavaloper',
19+
};
20+
21+
export const DEFAULTS = {
22+
senderAddress: 'kava1cyyzpxplxdzkeea7kwsydadg87357qnarn3sk9',
23+
pubKey: 'AuwYyCUBxQiBGSUWebU46c+OrlApVsyGLHd4qhSDZeiG',
24+
privateKey: 'SNI8xBejBnTpB6JAPxCfCC2S4ZeCPQLmpCPGrrjkEgQ=',
25+
recipientAddress1: 'kava18s5lynnmx37hq4wlrw9gdn68sg2uxp5rucdhve',
26+
recipientAddress2: 'kava1c6mj5v5wpjl6zs7lz0l6dlv3r4ccqaa4grcurq',
27+
sendMessageTypeUrl: '/cosmos.bank.v1beta1.MsgSend',
28+
sendAmount: '1000',
29+
feeAmount: '5000',
30+
gasLimit: 200000,
31+
validatorAddress1: 'kavavaloper1q8mgs55hfgkm7d5rret439997x87s2ek00qcs9',
32+
validatorAddress2: 'kavavaloper1ea4hlqfskjvn0ldenw8gv7jjdzrljcchxcmnqq',
33+
};
34+
35+
export const TEST_SEND_TX = {
36+
hash: 'A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2',
37+
signature: 'OnBWLyOUqNqX7h65Dtfna4W1vU4+OEVZPZLOffO+amNnHHwoFQZMnVK1+s4Y5/arizHXdbFTtrIG1ANWJz94zw==',
38+
signedTxBase64:
39+
'CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKK2thdmExY3l5enB4cGx4ZHprZWVhN2t3c3lkYWRnODczNTdxbmFybjNzazkSK2thdmExOHM1bHlubm14MzdocTR3bHJ3OWdkbjY4c2cydXhwNXJ1Y2RodmUaDQoFdWthdmESBDEwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuwYyCUBxQiBGSUWebU46c+OrlApVsyGLHd4qhSDZeiGEgQKAggBGBQSEwoNCgV1a2F2YRIENTAwMBDAmgwaQDpwVi8jlKjal+4euQ7X52uFtb1OPjhFWT2Szn3zvmpjZxx8KBUGTJ1StfrOGOf2q4sx13WxU7ayBtQDVic/eM8=',
40+
accountNumber: 182114,
41+
sequence: 20,
42+
sendAmount: '1000',
43+
};
44+
45+
export const TEST_SEND_TX2 = {
46+
hash: 'B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3',
47+
signature: 'nCRo4QhtTTldC+bQadH6Dqplm8s9UuRtXVmQqvVriKkWFGlO/n70VvbB+YuQ8sYc1q06AapkWpt/mD4HQbgG3w==',
48+
signedTxBase64:
49+
'CokBCoYBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmYKK2thdmExY3l5enB4cGx4ZHprZWVhN2t3c3lkYWRnODczNTdxbmFybjNzazkSK2thdmExOHM1bHlubm14MzdocTR3bHJ3OWdkbjY4c2cydXhwNXJ1Y2RodmUaCgoFdWthdmESATESZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuwYyCUBxQiBGSUWebU46c+OrlApVsyGLHd4qhSDZeiGEgQKAggBGBQSEwoNCgV1a2F2YRIENTAwMBDAmgwaQJwkaOEIbU05XQvm0GnR+g6qZZvLPVLkbV1ZkKr1a4ipFhRpTv5+9Fb2wfmLkPLGHNatOgGqZFqbf5g+B0G4Bt8=',
50+
accountNumber: 182114,
51+
sequence: 20,
52+
sendAmount: '1',
53+
};
54+
55+
export const TEST_SEND_MANY_TX = {
56+
hash: 'C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4',
57+
signature: 'Opz+KYGr/pjC+iaYytf4PqRcgvFxQ3Lf0+1o3pGub0xlA6QbkySn7WS+c06rx4f27DTjHrhNflvCdhXmTCHulQ==',
58+
signedTxBase64:
59+
'CpICCoYBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmYKK2thdmExY3l5enB4cGx4ZHprZWVhN2t3c3lkYWRnODczNTdxbmFybjNzazkSK2thdmExOHM1bHlubm14MzdocTR3bHJ3OWdkbjY4c2cydXhwNXJ1Y2RodmUaCgoFdWthdmESATEKhgEKHC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQSZgora2F2YTFjeXl6cHhwbHhkemtlZWE3a3dzeWRhZGc4NzM1N3FuYXJuM3NrORIra2F2YTFjNm1qNXY1d3BqbDZ6czdsejBsNmRsdjNyNGNjcWFhNGdyY3VycRoKCgV1a2F2YRIBMhJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEC7BjIJQHFCIEZJRZ5tTjpz46uUClWzIYsd3iqFINl6IYSBAoCCAEYFBITCg0KBXVrYXZhEgQ1MDAwEMCaDBpAOpz+KYGr/pjC+iaYytf4PqRcgvFxQ3Lf0+1o3pGub0xlA6QbkySn7WS+c06rx4f27DTjHrhNflvCdhXmTCHulQ==',
60+
accountNumber: 182114,
61+
sequence: 20,
62+
sendAmount: '1',
63+
sendAmount2: '2',
64+
recipient2: 'kava1c6mj5v5wpjl6zs7lz0l6dlv3r4ccqaa4grcurq',
65+
};
66+
67+
export const TEST_TX_WITH_MEMO = {
68+
hash: 'D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5',
69+
signature: '5g4qRY2kii/w1vXRXbuGGOTbDZ9Ne+MeWyPNAXyNOvUM1hbhYGIVz0xI+J10B+Ugw/GqSSB6W1lxbzvAy/mQBg==',
70+
signedTxBase64:
71+
'CqcBCogBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmgKK2thdmExY3l5enB4cGx4ZHprZWVhN2t3c3lkYWRnODczNTdxbmFybjNzazkSK2thdmExOHM1bHlubm14MzdocTR3bHJ3OWdkbjY4c2cydXhwNXJ1Y2RodmUaDAoFdWthdmESAzUwMBIaVGVzdDEyM0FscGhhbnVtZXJpY01lbW80NTYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuwYyCUBxQiBGSUWebU46c+OrlApVsyGLHd4qhSDZeiGEgQKAggBGBQSEwoNCgV1a2F2YRIENTAwMBDAmgwaQOYOKkWNpIov8Nb10V27hhjk2w2fTXvjHlsjzQF8jTr1DNYW4WBiFc9MSPiddAflIMPxqkkgeltZcW87wMv5kAY=',
72+
accountNumber: 182114,
73+
sequence: 20,
74+
sendAmount: '500',
75+
memo: 'Test123AlphanumericMemo456',
76+
};
77+
78+
export const blockHashes = {
79+
hash1: 'E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6',
80+
hash2: 'F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1',
81+
};
82+
83+
// Generate the complete kava test data
84+
export const kava = generateCoinData(chainConfig, DEFAULTS, blockHashes, {
85+
TEST_SEND_TX,
86+
TEST_SEND_TX2,
87+
TEST_SEND_MANY_TX,
88+
TEST_TX_WITH_MEMO,
89+
});
90+
91+
export default kava;

modules/sdk-coin-polyx/src/lib/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ export const POLYX_ADDRESS_FORMAT = 12;
1111
* Tpolyx testnet address format
1212
*/
1313
export const TPOLYX_ADDRESS_FORMAT = 42;
14+
15+
/**
16+
* Regex pattern for validating Polymesh DID (Decentralized Identifier)
17+
* DIDs are 32-byte hex strings (0x prefix + 64 hex characters)
18+
*/
19+
export const POLYX_DID_REGEX = /^0x[a-fA-F0-9]{64}$/;

modules/sdk-coin-polyx/src/lib/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,17 @@ export { BatchStakingBuilder as BatchBuilder } from './batchStakingBuilder';
2121
export { BatchUnstakingBuilder } from './batchUnstakingBuilder';
2222
export { UnbondBuilder } from './unbondBuilder';
2323
export { WithdrawUnbondedBuilder } from './withdrawUnbondedBuilder';
24+
import polyxUtils from './utils';
2425
export { Utils, default as utils } from './utils';
2526
export * from './iface';
2627

2728
export { BondArgs, NominateArgs, BatchCallObject, BatchArgs } from './iface';
29+
30+
/**
31+
* Checks if a string is a valid Polymesh DID (Decentralized Identifier)
32+
* DIDs are 32-byte hex strings (0x prefix + 64 hex characters)
33+
*
34+
* @param {string} did - The string to validate
35+
* @returns {boolean} true if valid DID format, false otherwise
36+
*/
37+
export const isValidDid = (did: string): boolean => polyxUtils.isValidDid(did);

0 commit comments

Comments
 (0)