Skip to content

Commit

Permalink
adds Solana signer
Browse files Browse the repository at this point in the history
  • Loading branch information
netbonus committed Jul 23, 2024
1 parent ad7f6e8 commit 133dded
Show file tree
Hide file tree
Showing 4 changed files with 2,666 additions and 2,362 deletions.
1 change: 1 addition & 0 deletions packages/solana/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"bs58": "5.0.0",
"ed25519-hd-key": "1.3.0",
"eslint-config-custom": "*",
"gridplus-sdk": "^2.6.0",
"lodash": "4.17.21",
"reflect-metadata": "0.1.13",
"rimraf": "4.4.0",
Expand Down
92 changes: 92 additions & 0 deletions packages/solana/src/signers/lattice.signer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Msg } from '@xdefi-tech/chains-core';
import { Transaction, PublicKey } from '@solana/web3.js';
import fetch from 'cross-fetch';

import { SolanaProvider } from '../chain.provider';
import { IndexerDataSource } from '../datasource';
import { SOLANA_MANIFEST } from '../manifests';
import { ChainMsg, MsgBody } from '../msg';

import LatticeSigner from './lattice.signer';

globalThis.fetch = fetch;

/**
* Update this config to match your Lattice device
*
* TODO: Update this to use environment variables
*/
const CONFIG = {
deviceId: 'sc6J1U',
password: 'asdfasdf',
name: 'SDK Test2',
};

jest.mock('@solana/web3.js', () => {
const original = jest.requireActual('@solana/web3.js');
return {
...original,
Transaction: {
...original.Transaction,
prototype: {
...original.Transaction.prototype,
serialize: jest
.fn()
.mockReturnValue(Buffer.from('serialized_transaction')),
},
},
};
});

describe('lattice.signer', () => {
let signer: LatticeSigner;
let derivationPath: string;
let provider: SolanaProvider;
let txInput: MsgBody;
let message: Msg;

beforeAll(async () => {
//@ts-ignore
signer = await LatticeSigner.create(CONFIG);
if (!signer.isPaired) {
throw new Error('Failed to pair with Lattice device');
}
});

beforeEach(async () => {
provider = new SolanaProvider(new IndexerDataSource(SOLANA_MANIFEST));
derivationPath = "m/44'/501'/0'/0/0";

txInput = {
from: '7HZYYfdqQgDgNduLA5gh8y4A5Mr3rCLVWeXBF4Vg9qZZ',
to: 'GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5',
amount: 0.000001,
gasPrice: 5000,
priorityFeeAmount: 10000,
};

message = provider.createMsg(txInput);
});

it('should get an address from the lattice device', async () => {
const address = await signer.getAddress(derivationPath);
expect(address).toBe(txInput.from);
}, 100000);

it('should sign a transaction using a lattice device', async () => {
await signer.sign(message as ChainMsg, derivationPath);
expect(message.signedTransaction).toBeTruthy();
}, 100000);

it('should return false when verifying an invalid address', async () => {
expect(signer.verifyAddress('invalid_address')).toBe(false);
});

it('should validate a correct address', async () => {
expect(signer.verifyAddress(txInput.from)).toBe(true);
});

it('should fail if private key is requested', async () => {
await expect(signer.getPrivateKey(derivationPath)).rejects.toThrow();
});
});
69 changes: 69 additions & 0 deletions packages/solana/src/signers/lattice.signer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { PublicKey, Transaction as SolanaTransaction } from '@solana/web3.js';
import { Signer, SignerDecorator } from '@xdefi-tech/chains-core';
import { fetchAddresses, signSolanaTx } from 'gridplus-sdk';

import { ChainMsg } from '../msg';

interface SolanaTransactionData {
transaction: Buffer;
signerPath: number[];
}

@SignerDecorator(Signer.SignerType.LATTICE)
export class LatticeSigner extends Signer.LatticeProvider {
verifyAddress(address: string): boolean {
try {
const publicKey = new PublicKey(address);
return publicKey.toBase58() === address;
} catch (error) {
return false;
}
}

async getPrivateKey(_derivation: string) {
throw new Error('Cannot extract private key from Lattice device');
}

async getAddress(derivation: string): Promise<string> {
const addresses = await fetchAddresses({
startPath:
Signer.LatticeProvider.convertDerivationPathToArray(derivation),
n: 1,
flag: 0x04,
});
return addresses[0];
}

async sign(msg: ChainMsg, derivation: string): Promise<void> {
const tx = await msg.buildTx();
const payload = this.convertToSolanaTransactionData(tx as any, derivation);
const signedTx = await this.signSolanaTransaction(payload);
msg.sign(signedTx.serialize());
}

private convertToSolanaTransactionData(
transaction: SolanaTransaction,
derivation: string
): SolanaTransactionData {
const signerPath =
Signer.LatticeProvider.convertDerivationPathToArray(derivation);
return {
transaction: Buffer.from(transaction.serializeMessage()),
signerPath,
};
}

private async signSolanaTransaction(
payload: SolanaTransactionData
): Promise<SolanaTransaction> {
const { transaction, signerPath } = payload;

const signedTx = await signSolanaTx(transaction, {
signerPath,
});

return signedTx;
}
}

export default LatticeSigner;
Loading

0 comments on commit 133dded

Please sign in to comment.