Skip to content

Commit 133dded

Browse files
committed
adds Solana signer
1 parent ad7f6e8 commit 133dded

4 files changed

Lines changed: 2666 additions & 2362 deletions

File tree

packages/solana/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"bs58": "5.0.0",
3535
"ed25519-hd-key": "1.3.0",
3636
"eslint-config-custom": "*",
37+
"gridplus-sdk": "^2.6.0",
3738
"lodash": "4.17.21",
3839
"reflect-metadata": "0.1.13",
3940
"rimraf": "4.4.0",
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Msg } from '@xdefi-tech/chains-core';
2+
import { Transaction, PublicKey } from '@solana/web3.js';
3+
import fetch from 'cross-fetch';
4+
5+
import { SolanaProvider } from '../chain.provider';
6+
import { IndexerDataSource } from '../datasource';
7+
import { SOLANA_MANIFEST } from '../manifests';
8+
import { ChainMsg, MsgBody } from '../msg';
9+
10+
import LatticeSigner from './lattice.signer';
11+
12+
globalThis.fetch = fetch;
13+
14+
/**
15+
* Update this config to match your Lattice device
16+
*
17+
* TODO: Update this to use environment variables
18+
*/
19+
const CONFIG = {
20+
deviceId: 'sc6J1U',
21+
password: 'asdfasdf',
22+
name: 'SDK Test2',
23+
};
24+
25+
jest.mock('@solana/web3.js', () => {
26+
const original = jest.requireActual('@solana/web3.js');
27+
return {
28+
...original,
29+
Transaction: {
30+
...original.Transaction,
31+
prototype: {
32+
...original.Transaction.prototype,
33+
serialize: jest
34+
.fn()
35+
.mockReturnValue(Buffer.from('serialized_transaction')),
36+
},
37+
},
38+
};
39+
});
40+
41+
describe('lattice.signer', () => {
42+
let signer: LatticeSigner;
43+
let derivationPath: string;
44+
let provider: SolanaProvider;
45+
let txInput: MsgBody;
46+
let message: Msg;
47+
48+
beforeAll(async () => {
49+
//@ts-ignore
50+
signer = await LatticeSigner.create(CONFIG);
51+
if (!signer.isPaired) {
52+
throw new Error('Failed to pair with Lattice device');
53+
}
54+
});
55+
56+
beforeEach(async () => {
57+
provider = new SolanaProvider(new IndexerDataSource(SOLANA_MANIFEST));
58+
derivationPath = "m/44'/501'/0'/0/0";
59+
60+
txInput = {
61+
from: '7HZYYfdqQgDgNduLA5gh8y4A5Mr3rCLVWeXBF4Vg9qZZ',
62+
to: 'GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5',
63+
amount: 0.000001,
64+
gasPrice: 5000,
65+
priorityFeeAmount: 10000,
66+
};
67+
68+
message = provider.createMsg(txInput);
69+
});
70+
71+
it('should get an address from the lattice device', async () => {
72+
const address = await signer.getAddress(derivationPath);
73+
expect(address).toBe(txInput.from);
74+
}, 100000);
75+
76+
it('should sign a transaction using a lattice device', async () => {
77+
await signer.sign(message as ChainMsg, derivationPath);
78+
expect(message.signedTransaction).toBeTruthy();
79+
}, 100000);
80+
81+
it('should return false when verifying an invalid address', async () => {
82+
expect(signer.verifyAddress('invalid_address')).toBe(false);
83+
});
84+
85+
it('should validate a correct address', async () => {
86+
expect(signer.verifyAddress(txInput.from)).toBe(true);
87+
});
88+
89+
it('should fail if private key is requested', async () => {
90+
await expect(signer.getPrivateKey(derivationPath)).rejects.toThrow();
91+
});
92+
});
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { PublicKey, Transaction as SolanaTransaction } from '@solana/web3.js';
2+
import { Signer, SignerDecorator } from '@xdefi-tech/chains-core';
3+
import { fetchAddresses, signSolanaTx } from 'gridplus-sdk';
4+
5+
import { ChainMsg } from '../msg';
6+
7+
interface SolanaTransactionData {
8+
transaction: Buffer;
9+
signerPath: number[];
10+
}
11+
12+
@SignerDecorator(Signer.SignerType.LATTICE)
13+
export class LatticeSigner extends Signer.LatticeProvider {
14+
verifyAddress(address: string): boolean {
15+
try {
16+
const publicKey = new PublicKey(address);
17+
return publicKey.toBase58() === address;
18+
} catch (error) {
19+
return false;
20+
}
21+
}
22+
23+
async getPrivateKey(_derivation: string) {
24+
throw new Error('Cannot extract private key from Lattice device');
25+
}
26+
27+
async getAddress(derivation: string): Promise<string> {
28+
const addresses = await fetchAddresses({
29+
startPath:
30+
Signer.LatticeProvider.convertDerivationPathToArray(derivation),
31+
n: 1,
32+
flag: 0x04,
33+
});
34+
return addresses[0];
35+
}
36+
37+
async sign(msg: ChainMsg, derivation: string): Promise<void> {
38+
const tx = await msg.buildTx();
39+
const payload = this.convertToSolanaTransactionData(tx as any, derivation);
40+
const signedTx = await this.signSolanaTransaction(payload);
41+
msg.sign(signedTx.serialize());
42+
}
43+
44+
private convertToSolanaTransactionData(
45+
transaction: SolanaTransaction,
46+
derivation: string
47+
): SolanaTransactionData {
48+
const signerPath =
49+
Signer.LatticeProvider.convertDerivationPathToArray(derivation);
50+
return {
51+
transaction: Buffer.from(transaction.serializeMessage()),
52+
signerPath,
53+
};
54+
}
55+
56+
private async signSolanaTransaction(
57+
payload: SolanaTransactionData
58+
): Promise<SolanaTransaction> {
59+
const { transaction, signerPath } = payload;
60+
61+
const signedTx = await signSolanaTx(transaction, {
62+
signerPath,
63+
});
64+
65+
return signedTx;
66+
}
67+
}
68+
69+
export default LatticeSigner;

0 commit comments

Comments
 (0)