Skip to content

Commit

Permalink
Merge branch 'main' into feat/solana
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonioVentilii authored Jan 10, 2025
2 parents 46ca326 + b9f8bd6 commit 161787a
Show file tree
Hide file tree
Showing 13 changed files with 374 additions and 54 deletions.
3 changes: 3 additions & 0 deletions .github/repo_policies/BOT_APPROVED_FILES
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

package.json
package-lock.json
Cargo.toml
Cargo.lock
rust-toolchain.toml
e2e/**/*.png
declarations/**/*
src/frontend/src/env/tokens/tokens.*.json
Expand Down
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[toolchain]
channel = "1.83.0"
channel = "1.84.0"
targets = ["wasm32-unknown-unknown"]
2 changes: 1 addition & 1 deletion scripts/build.csp.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const injectLinkPreloader = (indexHtml) => {
const extractStartScript = (htmlFile) => {
const indexHtml = readFileSync(htmlFile, 'utf8');

const svelteKitStartScript = /(<script>)([\s\S]*?)(<\/script>)/gm;
const svelteKitStartScript = /(<script>)([\s\S]*?)(<\/script>)/gim;

// 1. extract SvelteKit start script to a separate main.js file
const [_script, _scriptStartTag, content, _scriptEndTag] = svelteKitStartScript.exec(indexHtml);
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/src/icp-eth/components/info/InfoEthereum.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@
>
</h4>

<p class="mt-3 text-misty-rose">
<p class="mt-3 text-black opacity-50">
{replacePlaceholders(replaceOisyPlaceholders($i18n.info.ethereum.description), {
$token: twinToken.symbol,
$ckToken: ckTokenSymbol,
$network: twinToken.network.name
})}
</p>

<p class="mt-3 text-misty-rose">
<p class="mt-3 text-black opacity-50">
{replacePlaceholders(replaceOisyPlaceholders($i18n.info.ethereum.note), {
$token: twinToken.symbol,
$ckToken: ckTokenSymbol
Expand Down
17 changes: 8 additions & 9 deletions src/frontend/src/lib/api/signer.api.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type {
BitcoinNetwork,
EthSignTransactionRequest,
SchnorrKeyId,
SendBtcResponse
} from '$declarations/signer/signer.did';
import { SignerCanister } from '$lib/canisters/signer.canister';
import { SIGNER_CANISTER_ID } from '$lib/constants/app.constants';
import type { BtcAddress, EthAddress } from '$lib/types/address';
import type { SendBtcParams } from '$lib/types/api';
import type {
GetSchnorrPublicKeyParams,
SendBtcParams,
SignWithSchnorrParams
} from '$lib/types/api';
import type { CanisterApiFunctionParams } from '$lib/types/canister';
import { Principal } from '@dfinity/principal';
import { assertNonNullish, isNullish } from '@dfinity/utils';
Expand Down Expand Up @@ -90,20 +93,16 @@ export const sendBtc = async ({
export const getSchnorrPublicKey = async ({
identity,
...rest
}: CanisterApiFunctionParams<{ derivationPath: string[]; keyId: SchnorrKeyId }>): Promise<
Uint8Array | number[]
> => {
}: CanisterApiFunctionParams<GetSchnorrPublicKeyParams>): Promise<Uint8Array | number[]> => {
const { getSchnorrPublicKey } = await signerCanister({ identity });

return await getSchnorrPublicKey(rest);
};

export const signSchnorrPublicKey = async ({
export const signWithSchnorr = async ({
identity,
...rest
}: CanisterApiFunctionParams<{ derivationPath: string[]; message: number[] }>): Promise<
Uint8Array | number[]
> => {
}: CanisterApiFunctionParams<SignWithSchnorrParams>): Promise<Uint8Array | number[]> => {
const { signWithSchnorr } = await signerCanister({ identity });

return await signWithSchnorr(rest);
Expand Down
23 changes: 10 additions & 13 deletions src/frontend/src/lib/canisters/signer.canister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type {
EthSignPrehashRequest,
EthSignTransactionRequest,
GetBalanceRequest,
SchnorrKeyId,
SendBtcResponse,
_SERVICE as SignerService
} from '$declarations/signer/signer.did';
Expand All @@ -14,7 +13,11 @@ import { idlFactory as idlFactorySigner } from '$declarations/signer/signer.fact
import { getAgent } from '$lib/actors/agents.ic';
import { P2WPKH, SIGNER_PAYMENT_TYPE } from '$lib/canisters/signer.constants';
import type { BtcAddress, EthAddress } from '$lib/types/address';
import type { SendBtcParams } from '$lib/types/api';
import type {
GetSchnorrPublicKeyParams,
SendBtcParams,
SignWithSchnorrParams
} from '$lib/types/api';
import type { CreateCanisterOptions } from '$lib/types/canister';
import { mapDerivationPath } from '$lib/utils/signer.utils';
import { Canister, createServices, toNullable } from '@dfinity/utils';
Expand Down Expand Up @@ -201,10 +204,7 @@ export class SignerCanister extends Canister<SignerService> {
getSchnorrPublicKey = async ({
derivationPath,
keyId
}: {
derivationPath: string[];
keyId: SchnorrKeyId;
}): Promise<Uint8Array | number[]> => {
}: GetSchnorrPublicKeyParams): Promise<Uint8Array | number[]> => {
const { schnorr_public_key } = this.caller({
certified: true
});
Expand All @@ -229,19 +229,16 @@ export class SignerCanister extends Canister<SignerService> {

signWithSchnorr = async ({
message,
derivationPath
}: {
message: number[];
derivationPath: string[];
}): Promise<Uint8Array | number[]> => {
derivationPath,
keyId
}: SignWithSchnorrParams): Promise<Uint8Array | number[]> => {
const { schnorr_sign } = this.caller({
certified: true
});

const response = await schnorr_sign(
{
// TODO: set the key_id in the signer repo, as done for the ecdsa key
key_id: { algorithm: { ed25519: null }, name: 'dfx_test_key' },
key_id: keyId,
derivation_path: mapDerivationPath(derivationPath),
message
},
Expand Down
10 changes: 3 additions & 7 deletions src/frontend/src/lib/components/info/InfoBox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,10 @@
</script>

{#if !hideInfo}
<div
class="relative mb-12 rounded-lg border-2 border-dust bg-white px-6 py-4"
transition:slide={SLIDE_EASING}
>
<button class="text absolute right-2 top-2" on:click aria-label={$i18n.core.text.close}
><IconClose size="24px" /></button
<div class="relative mb-12 rounded-lg bg-white px-6 py-4" transition:slide={SLIDE_EASING}>
<button class="absolute right-2 top-2 text-tertiary" on:click aria-label={$i18n.core.text.close}
><IconClose /></button
>

<slot />
</div>
{/if}
10 changes: 10 additions & 0 deletions src/frontend/src/lib/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
import type { TxId } from '$declarations/kong_backend/kong_backend.did';
import type {
BtcTxOutput,
SchnorrKeyId,
BitcoinNetwork as SignerBitcoinNetwork,
Utxo as SignerUtxo
} from '$declarations/signer/signer.did';
Expand Down Expand Up @@ -49,6 +50,15 @@ export interface SendBtcParams {
outputs: BtcTxOutput[];
}

export interface GetSchnorrPublicKeyParams {
derivationPath: string[];
keyId: SchnorrKeyId;
}

export interface SignWithSchnorrParams extends GetSchnorrPublicKeyParams {
message: number[];
}

export interface AddUserHiddenDappIdParams {
dappId: string;
currentUserVersion?: bigint;
Expand Down
108 changes: 107 additions & 1 deletion src/frontend/src/sol/api/solana.api.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { WALLET_PAGINATION } from '$lib/constants/app.constants';
import type { SolAddress } from '$lib/types/address';
import { last } from '$lib/utils/array.utils';
import { solanaHttpRpc } from '$sol/providers/sol-rpc.providers';
import type { SolanaNetworkType } from '$sol/types/network';
import { address as solAddress } from '@solana/addresses';
import type { SolRpcTransaction, SolSignature } from '$sol/types/sol-transaction';
import { isNullish, nonNullish } from '@dfinity/utils';
import { address as solAddress, type Address } from '@solana/addresses';
import { signature, type Signature } from '@solana/keys';
import type { Lamports } from '@solana/rpc-types';
import type { Writeable } from 'zod';

//lamports are like satoshis: https://solana.com/docs/terminology#lamport
export const loadSolLamportsBalance = async ({
Expand All @@ -19,3 +25,103 @@ export const loadSolLamportsBalance = async ({

return balance;
};

/**
* Fetches transactions without an error for a given wallet address.
*/
export const getSolTransactions = async ({
address,
network,
before,
limit = Number(WALLET_PAGINATION)
}: {
address: SolAddress;
network: SolanaNetworkType;
before?: string;
limit?: number;
}): Promise<SolRpcTransaction[]> => {
const wallet = solAddress(address);
const beforeSignature = nonNullish(before) ? signature(before) : undefined;
const signatures = await fetchSignatures({ network, wallet, before: beforeSignature, limit });

const transactions = await signatures.reduce(
async (accPromise, signature) => {
const acc = await accPromise;
const transactionDetail = await fetchTransactionDetailForSignature({ signature, network });
if (nonNullish(transactionDetail)) {
acc.push(transactionDetail);
}
return acc;
},
Promise.resolve([] as SolRpcTransaction[])
);

return transactions.slice(0, limit);
};

/**
* Fetches signatures without an error for a given wallet address.
*/
const fetchSignatures = async ({
network,
wallet,
before,
limit
}: {
network: SolanaNetworkType;
wallet: Address;
before?: Signature;
limit: number;
}): Promise<SolSignature[]> => {
const { getSignaturesForAddress } = solanaHttpRpc(network);

let accumulatedSignatures: SolSignature[] = [];

const fetchSignaturesBatch = async (before: Signature | undefined): Promise<SolSignature[]> => {
const fetchedSignatures = await getSignaturesForAddress(wallet, {
before,
limit
}).send();

const successfulSignatures = fetchedSignatures.filter(({ err }) => isNullish(err));

accumulatedSignatures = [...accumulatedSignatures, ...successfulSignatures];

const hasLoadedEnoughTransactions = accumulatedSignatures.length >= limit;
const hasNoMoreSignaturesLeft = fetchedSignatures.length < limit;

if (hasLoadedEnoughTransactions || hasNoMoreSignaturesLeft) {
return accumulatedSignatures.slice(0, limit);
}

const lastSignature = last(fetchedSignatures as Writeable<typeof fetchedSignatures>)?.signature;
return fetchSignaturesBatch(lastSignature);
};

return await fetchSignaturesBatch(before);
};

const fetchTransactionDetailForSignature = async ({
signature: { signature, confirmationStatus },
network
}: {
signature: SolSignature;
network: SolanaNetworkType;
}): Promise<SolRpcTransaction | null> => {
const { getTransaction } = solanaHttpRpc(network);

const rpcTransaction = await getTransaction(signature, {
maxSupportedTransactionVersion: 0
}).send();

if (isNullish(rpcTransaction)) {
return null;
}

return {
...rpcTransaction,
version: rpcTransaction.version,

Check failure on line 123 in src/frontend/src/sol/api/solana.api.ts

View workflow job for this annotation

GitHub Actions / test

Type '{ version: TransactionVersion; confirmationStatus: Commitment | null; id: string; blockTime: UnixTimestamp | null; slot: bigint; meta: (Readonly<...> & ... 1 more ... & (Record<...> | Readonly<...>)) | null; transaction: Readonly<...> & ... 1 more ... & (Record<...> | Readonly<...>); } | { ...; }' is not assignable to type 'SolRpcTransaction | null'.
confirmationStatus,
id: signature.toString()
};
};
19 changes: 12 additions & 7 deletions src/frontend/src/sol/types/sol-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@ import type {
TransactionError,
UnixTimestamp
} from '@solana/rpc-types';
import type { GetSignaturesForAddressApi, GetTransactionApi } from '@solana/rpc';

export type SolTransactionType = Extract<
TransactionType,
(typeof solTransactionTypes.options)[number]
>;

export interface SolTransactionUi extends TransactionUiCommon {
id: string;
type: SolTransactionType;
status: Commitment | null;
value?: bigint;
fee?: bigint;
}

export interface SolRpcTransaction {
id: string;
blockTime: UnixTimestamp | null;
Expand Down Expand Up @@ -44,10 +53,6 @@ export interface SolRpcTransaction {
};
}

export interface SolTransactionUi extends TransactionUiCommon {
id: string;
type: SolTransactionType;
status: Commitment | null;
value?: bigint;
fee?: bigint;
}
export type SolSignature = ReturnType<
GetSignaturesForAddressApi['getSignaturesForAddress']
>[number];
25 changes: 15 additions & 10 deletions src/frontend/src/tests/lib/canisters/signer.canister.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ describe('signer.canister', () => {
});

describe('getSchnorrPublicKey', () => {
it('returns correct schnorr public key', async () => {
it('returns correct Schnorr public key', async () => {
const publicKey = [1, 2, 3];
const response = { public_key: publicKey, chain_code: [4, 5, 6] };
service.schnorr_public_key.mockResolvedValue({ Ok: [response] });
Expand Down Expand Up @@ -623,24 +623,28 @@ describe('signer.canister', () => {
});

describe('signWithSchnorr', () => {
it('signs with schnorr', async () => {
service.schnorr_sign.mockResolvedValue({ Ok: [{ signature: [4, 5, 6] }] });
const message = [1, 2, 3];
const signature = [4, 5, 6];

it('signs with Schnorr', async () => {
service.schnorr_sign.mockResolvedValue({ Ok: [{ signature }] });

const { signWithSchnorr } = await createSignerCanister({
serviceOverride: service
});

const res = await signWithSchnorr({
message: [1, 2, 3],
derivationPath: ['test']
message,
derivationPath: ['test'],
keyId: SOLANA_KEY_ID
});

expect(res).toEqual([4, 5, 6]);
expect(res).toEqual(signature);
expect(service.schnorr_sign).toHaveBeenCalledWith(
{
key_id: { algorithm: { ed25519: null }, name: 'dfx_test_key' },
key_id: SOLANA_KEY_ID,
derivation_path: mapDerivationPath(['test']),
message: [1, 2, 3]
message
},
[SIGNER_PAYMENT_TYPE]
);
Expand All @@ -656,8 +660,9 @@ describe('signer.canister', () => {
});

const res = signWithSchnorr({
message: [1, 2, 3],
derivationPath: ['test']
message,
derivationPath: ['test'],
keyId: SOLANA_KEY_ID
});

await expect(res).rejects.toThrow(mockResponseError);
Expand Down
Loading

0 comments on commit 161787a

Please sign in to comment.