Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions modules/sdk-coin-sol/src/sol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1700,11 +1700,17 @@ export class Sol extends BaseCoin {
}

const bitgoKey = params.bitgoKey.replace(/\s/g, '');
const MPC = await EDDSAMethods.getInitializedMpcInstance();
const userKey = params.userKey?.replace(/\s/g, '') ?? '';

const isMpcV2 = params.walletPassphrase
? !(await EDDSAUtils.isEddsaMpcV1SigningMaterial(userKey, params.walletPassphrase, this.bitgo))
: false;

const index = params.index || 0;
const currPath = params.seed ? getDerivationPath(params.seed) + `/${index}` : `m/${index}`;
const accountId = MPC.deriveUnhardened(bitgoKey, currPath).slice(0, 64);
const accountId = isMpcV2
? deriveUnhardenedMps(bitgoKey, currPath).slice(0, 64)
: (await EDDSAMethods.getInitializedMpcInstance()).deriveUnhardened(bitgoKey, currPath).slice(0, 64);
const bs58EncodedPublicKey = new SolKeyPair({ pub: accountId }).getAddress();

const blockhash = await this.getBlockhash(params.apiKey);
Expand All @@ -1726,7 +1732,8 @@ export class Sol extends BaseCoin {
const recoverNestedTxn = await this.signAndGenerateBroadcastableTransaction(
params,
txBuilder,
bs58EncodedPublicKey
bs58EncodedPublicKey,
isMpcV2
);

const serializedTxn = (await recoverNestedTxn).serializedTx;
Expand Down
80 changes: 80 additions & 0 deletions modules/sdk-coin-sol/test/unit/sol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3449,6 +3449,86 @@ describe('SOL:', function () {
should.equal(addSignatureSpy.firstCall.args[0].pub, mpcV2WalletAddress);
});
});

describe('recoverNestedAta', () => {
let nestedAtaMpcV2Params: SolRecoveryOptions;

before(function () {
nestedAtaMpcV2Params = {
userKey: mpcV2UserKey,
backupKey: mpcV2BackupKey,
bitgoKey: mpcV2CommonKeyChain,
recoveryDestination: testData.closeATAkeys.destinationPubKey,
walletPassphrase: testData.keys.walletPassword,
nestedAtaAddress: 'FGuZSBhtreqSUsE86xokyjKz2i8VBtJzy6uMXXKyGHug',
ownerAtaAddress: 'Zfm98ZpVafydhFTYcsY6bHgubhB4cFgWFvbdEJxYhTA',
tokenMintAddress: 'ZBCNpuD7YMXzTHB2fhGkGi78MNsHGLRXUhRewNRm9RU',
};
});

beforeEach(() => {
mpcV2SandBox.stub(Sol.prototype, 'broadcastTransaction' as keyof Sol).resolves({
txId: testData.SolResponses.broadcastTransactionResponse.body.result,
});
});

it('should recover nested ATA with MPCv2 keycard using MPS-derived wallet address', async function () {
const getTSSSignatureSpy = mpcV2SandBox.spy(EDDSAMethods, 'getTSSSignature');

const result = await basecoin.recoverNestedAta(nestedAtaMpcV2Params);

result.should.not.be.empty();
should.equal(result.txId, testData.SolResponses.broadcastTransactionResponse.body.result);
should.equal(
callBack.getCalls().find((call) => call.args[0]?.payload?.method === 'getLatestBlockhash') !== undefined,
true
);
mpcV2SandBox.assert.notCalled(getTSSSignatureSpy);
});

it('should recover nested ATA with MPCv1 keycard using MPC.deriveUnhardened (regression)', async function () {
const getTSSSignatureSpy = mpcV2SandBox.spy(EDDSAMethods, 'getTSSSignature');

const result = await basecoin.recoverNestedAta({
userKey: testData.closeATAkeys.userKey,
backupKey: testData.closeATAkeys.backupKey,
bitgoKey: testData.closeATAkeys.bitgoKey,
recoveryDestination: testData.closeATAkeys.destinationPubKey,
walletPassphrase: testData.closeATAkeys.walletPassword,
nestedAtaAddress: 'FGuZSBhtreqSUsE86xokyjKz2i8VBtJzy6uMXXKyGHug',
ownerAtaAddress: 'Zfm98ZpVafydhFTYcsY6bHgubhB4cFgWFvbdEJxYhTA',
tokenMintAddress: 'ZBCNpuD7YMXzTHB2fhGkGi78MNsHGLRXUhRewNRm9RU',
});

result.should.not.be.empty();
should.equal(result.txId, testData.SolResponses.broadcastTransactionResponse.body.result);
mpcV2SandBox.assert.calledOnce(getTSSSignatureSpy);
});

it('should throw when MPCv2 recoverNestedAta bitgoKey does not match keycard commonKeyChain', async function () {
await basecoin
.recoverNestedAta({ ...nestedAtaMpcV2Params, bitgoKey: mismatchedBitgoKey })
.should.be.rejectedWith('EdDSA MPCv2 recovery: commonKeyChain from keycard does not match bitgoKey');
});

it('should throw when nestedAtaAddress is missing for recoverNestedAta MPCv2', async function () {
await basecoin
.recoverNestedAta({
...nestedAtaMpcV2Params,
nestedAtaAddress: undefined,
})
.should.be.rejectedWith('invalid nestedAtaAddress');
});

it('should throw when ownerAtaAddress is missing for recoverNestedAta MPCv2', async function () {
await basecoin
.recoverNestedAta({
...nestedAtaMpcV2Params,
ownerAtaAddress: undefined,
})
.should.be.rejectedWith('invalid ownerAtaAddress');
});
});
});

describe('Build Consolidation Recoveries:', () => {
Expand Down
Loading