Skip to content

Commit

Permalink
feat: add support for starknet account to offchain actions (#383)
Browse files Browse the repository at this point in the history
* feat: add setAlias to starknet eth sign

* fix: use starknet-sig instead of ethereum-sig

* fix: fix signature schema

* fix: update type to include optional properties

* fix: more robust starknet wallet detection

* fix: enable back follow button for starknet wallet

* fix: fix typing

* feat: add support for starknet signature for offchain `send`

* fix: use string type to allow starknet address

* fix: enable space following/unfollowing for starknet wallets

* fix: enable user profile edition for starknet users

* fix: use formatted address in alias from

* fix: always use padded starknet address

* fix: sign sn createAlias on the correct chainId

* chore: fix tests

* fix: avoid loading votes from incompatible networks

* fix: add types and domain to starknet envelope

* Update apps/ui/src/composables/useActions.ts

Co-authored-by: Chaitanya <yourchaitu@gmail.com>

* fix: revert unrelated change

* chore: removed unused import

* chore: add comments about confusing extra starknet params on evm envelope

* chore: fix import order

* fix: sign alias with first available starknet network

* chore: lint fix

* chore: add changesets

* revert: revert back ot use correct starknet chaind ID

* fix: finish revert

* fix: fix leftover from merge

* refactor: simplify starknetNetworkId lookup

---------

Co-authored-by: Chaitanya <yourchaitu@gmail.com>
Co-authored-by: Wiktor Tkaczyński <wiktor.tkaczynski@gmail.com>
  • Loading branch information
3 people authored Jul 29, 2024
1 parent f1e3b13 commit e088ca6
Show file tree
Hide file tree
Showing 17 changed files with 339 additions and 41 deletions.
5 changes: 5 additions & 0 deletions .changeset/calm-deers-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@snapshot-labs/sx": patch
---

add domain and types to starknet/starknet-sig envelope
5 changes: 5 additions & 0 deletions .changeset/funny-candles-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@snapshot-labs/sx": patch
---

always return padded addresses in starknet/starknet-sig envelope
5 changes: 5 additions & 0 deletions .changeset/red-mayflies-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@snapshot-labs/sx": patch
---

add support for sending starknet signed message to offchain/ethereum-sig
5 changes: 5 additions & 0 deletions .changeset/wild-news-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@snapshot-labs/sx": patch
---

add setAlias to starknet/starknet-sig
3 changes: 0 additions & 3 deletions apps/ui/src/components/ButtonFollow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ const { isSafeWallet } = useSafeWallet(
props.space.snapshot_chain_id
);
const followedSpacesStore = useFollowedSpacesStore();
const { web3 } = useWeb3();
const spaceFollowed = computed(() =>
followedSpacesStore.isFollowed(spaceIdComposite)
);
const hidden = computed(() => web3.value?.type === 'argentx');
const loading = computed(
() =>
Expand All @@ -28,7 +26,6 @@ const loading = computed(

<template>
<UiButton
v-if="!hidden"
:disabled="loading || isSafeWallet"
class="group"
:class="{ 'hover:border-skin-danger': spaceFollowed }"
Expand Down
22 changes: 20 additions & 2 deletions apps/ui/src/composables/useActions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { getInstance } from '@snapshot-labs/lock/plugins/vue3';
import { registerTransaction } from '@/helpers/mana';
import { convertToMetaTransactions } from '@/helpers/transactions';
import { getNetwork, getReadWriteNetwork, metadataNetwork } from '@/networks';
import {
enabledNetworks,

Check failure on line 5 in apps/ui/src/composables/useActions.ts

View workflow job for this annotation

GitHub Actions / lint-build-test

'enabledNetworks' is defined but never used
getNetwork,
getReadWriteNetwork,
metadataNetwork,
starknetNetworks

Check failure on line 9 in apps/ui/src/composables/useActions.ts

View workflow job for this annotation

GitHub Actions / lint-build-test

'starknetNetworks' is defined but never used
} from '@/networks';
import { STARKNET_CONNECTORS } from '@/networks/common/constants';
import { Connector, StrategyConfig } from '@/networks/types';
import {
Choice,
Expand All @@ -16,6 +23,13 @@ import {
VoteType
} from '@/types';

const offchainToStarknetIds: Record<string, NetworkID> = {
s: 'sn',
's-tn': 'sn-sep'
};

const starknetNetworkId = offchainToStarknetIds[metadataNetwork];

export function useActions() {
const { mixpanel } = useMixpanel();
const uiStore = useUiStore();
Expand Down Expand Up @@ -126,7 +140,11 @@ export function useActions() {
}

async function getAliasSigner() {
const network = getNetwork(metadataNetwork);
const network = getNetwork(
STARKNET_CONNECTORS.includes(web3.value.type as Connector)
? starknetNetworkId
: metadataNetwork
);

return alias.getAliasWallet(address =>
wrapPromise(metadataNetwork, network.actions.setAlias(auth.web3, address))
Expand Down
1 change: 1 addition & 0 deletions apps/ui/src/networks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const evmNetworks: NetworkID[] = [
'linea-testnet'
];
export const offchainNetworks: NetworkID[] = ['s', 's-tn'];
export const starknetNetworks: NetworkID[] = ['sn', 'sn-sep'];
// This network is used for aliases/follows/profiles/explore page.
export const metadataNetwork: NetworkID =
import.meta.env.VITE_METADATA_NETWORK || 's';
Expand Down
7 changes: 6 additions & 1 deletion apps/ui/src/networks/starknet/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,12 @@ export function createActions(
},
followSpace: () => {},
unfollowSpace: () => {},
setAlias: () => {},
setAlias(web3: any, alias: string) {
return starkSigClient.setAlias({
signer: web3.provider.account,
data: { alias }
});
},
updateUser: () => {},
updateStatement: () => {},
send: (envelope: any) => starkSigClient.send(envelope) // TODO: extract it out of client to common helper
Expand Down
5 changes: 2 additions & 3 deletions apps/ui/src/stores/followedSpaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,15 @@ export const useFollowedSpacesStore = defineStore('followedSpaces', () => {
watch(
[
() => web3.value.account,
() => web3.value.type,
() => web3.value.authLoading,
() => authInitiated.value
],
async ([web3, walletType, authLoading, authInitiated]) => {
async ([web3, authLoading, authInitiated]) => {
if (!authInitiated || authLoading) return;

followedSpacesLoaded.value = false;

if (!web3 || walletType === 'argentx') {
if (!web3) {
followedSpacesIds.value = [];
followedSpacesLoaded.value = true;
return;
Expand Down
5 changes: 1 addition & 4 deletions apps/ui/src/views/User.vue
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,7 @@ watchEffect(() => setTitle(`${user.value?.name || id.value} user profile`));
/>
<div class="absolute right-4 top-4 space-x-2 flex">
<DropdownShare :message="shareMsg" class="!px-0 w-[46px]" />
<UiTooltip
v-if="web3.account === user.id && web3.type !== 'argentx'"
title="Edit profile"
>
<UiTooltip v-if="web3.account === user.id" title="Edit profile">
<UiButton class="!px-0 w-[46px]" @click="modalOpenEditUser = true">
<IH-cog class="inline-block" />
</UiButton>
Expand Down
9 changes: 6 additions & 3 deletions packages/sx.js/src/clients/offchain/ethereum-sig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
weightedVoteTypes
} from './types';
import { offchainGoerli } from '../../../offchainNetworks';
import { OffchainNetworkConfig } from '../../../types';
import { OffchainNetworkConfig, SignatureData } from '../../../types';
import {
CancelProposal,
EIP712CancelProposalMessage,
Expand All @@ -38,7 +38,6 @@ import {
FollowSpace,
Propose,
SetAlias,
SignatureData,
UnfollowSpace,
UpdateProposal,
UpdateStatement,
Expand Down Expand Up @@ -120,9 +119,10 @@ export class EthereumSig {
signature: sig,
domain,
types,
primaryType,
message
} = envelope.signatureData!;
const payload = {
const payload: any = {
address,
sig,
data: {
Expand All @@ -132,6 +132,9 @@ export class EthereumSig {
}
};

// primaryType needs to be attached when sending starknet-sig generated payload
if (primaryType) payload.data.primaryType = primaryType;

const body = {
method: 'POST',
headers: {
Expand Down
6 changes: 3 additions & 3 deletions packages/sx.js/src/clients/offchain/ethereum-sig/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const cancelProposalTypes = {

export const followSpaceTypes = {
Follow: [
{ name: 'from', type: 'address' },
{ name: 'from', type: 'string' },
{ name: 'network', type: 'string' },
{ name: 'space', type: 'string' },
{ name: 'timestamp', type: 'uint64' }
Expand All @@ -112,7 +112,7 @@ export const followSpaceTypes = {

export const unfollowSpaceTypes = {
Unfollow: [
{ name: 'from', type: 'address' },
{ name: 'from', type: 'string' },
{ name: 'network', type: 'string' },
{ name: 'space', type: 'string' },
{ name: 'timestamp', type: 'uint64' }
Expand All @@ -129,7 +129,7 @@ export const aliasTypes = {

export const updateUserTypes = {
Profile: [
{ name: 'from', type: 'address' },
{ name: 'from', type: 'string' },
{ name: 'timestamp', type: 'uint64' },
{ name: 'profile', type: 'string' }
]
Expand Down
14 changes: 1 addition & 13 deletions packages/sx.js/src/clients/offchain/types.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
import {
TypedDataDomain,
TypedDataField
} from '@ethersproject/abstract-signer';
import { Privacy } from '../../types';
import { Privacy, SignatureData } from '../../types';

export type Choice = number | number[] | string | Record<string, number>;

export type SignatureData = {
address: string;
signature: string;
domain: TypedDataDomain;
types: Record<string, TypedDataField[]>;
message: Record<string, any>;
};

export type Envelope<
T extends
| Vote
Expand Down
45 changes: 42 additions & 3 deletions packages/sx.js/src/clients/starknet/starknet-sig/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import randomBytes from 'randombytes';
import { Account, CallData, shortString, typedData, uint256 } from 'starknet';
import {
Account,
CallData,
shortString,
typedData,
uint256,
validateAndParseAddress
} from 'starknet';
import {
aliasTypes,
baseDomain,
proposeTypes,
updateProposalTypes,
voteTypes
} from './types';
import {
Alias,
ClientConfig,
ClientOpts,
Envelope,
Propose,
SignatureData,
StarknetEIP712AliasMessage,
StarknetEIP712ProposeMessage,
StarknetEIP712UpdateProposalMessage,
StarknetEIP712VoteMessage,
Expand Down Expand Up @@ -60,6 +70,7 @@ export class StarknetSig {
| StarknetEIP712ProposeMessage
| StarknetEIP712UpdateProposalMessage
| StarknetEIP712VoteMessage
| StarknetEIP712AliasMessage
>(
signer: Account,
verifyingContract: string,
Expand All @@ -79,15 +90,16 @@ export class StarknetSig {
domain,
message
};

const signature = await signer.signMessage(data);

return {
address: signer.address,
address: validateAndParseAddress(signer.address),
signature: Array.isArray(signature)
? signature.map(v => `0x${BigInt(v).toString(16)}`)
: [`0x${signature.r.toString(16)}`, `0x${signature.s.toString(16)}`],
message,
domain,
types,
primaryType
};
}
Expand Down Expand Up @@ -217,4 +229,31 @@ export class StarknetSig {
data
};
}

public async setAlias({
signer,
data
}: {
signer: Account;
data: Alias;
}): Promise<Envelope<Alias>> {
const message = {
from: validateAndParseAddress(signer.address),
timestamp: parseInt((Date.now() / 1e3).toFixed()),
...data
};

const signatureData = await this.sign(
signer,
'',
message,
aliasTypes,
'SetAlias'
);

return {
signatureData,
data
};
}
}
9 changes: 9 additions & 0 deletions packages/sx.js/src/clients/starknet/starknet-sig/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,12 @@ export const updateProposalTypes = {
Strategy: sharedTypes.Strategy,
u256: sharedTypes.u256
};

export const aliasTypes = {
StarkNetDomain: domainTypes.StarkNetDomain,
SetAlias: [
{ name: 'from', type: 'ContractAddress' },
{ name: 'alias', type: 'string' },
{ name: 'timestamp', type: 'felt' }
]
};
22 changes: 19 additions & 3 deletions packages/sx.js/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { BigNumberish, Call, RpcProvider } from 'starknet';
import {
TypedDataDomain,
TypedDataField
} from '@ethersproject/abstract-signer';
import { BigNumberish, Call, RpcProvider, StarkNetType } from 'starknet';
import { NetworkConfig } from './networkConfig';
import { MetaTransaction } from '../utils/encoding';

Expand Down Expand Up @@ -133,14 +137,20 @@ export type Vote = {
choice: Choice;
};

export type Message = Propose | Vote | UpdateProposal;
export type Alias = {
alias: string;
};

export type Message = Propose | Vote | UpdateProposal | Alias;

export type SignatureData = {
address: string;
commitTxId?: string;
commitHash?: string;
signature?: string[] | null;
signature?: string | string[] | null;
message?: Record<string, any>;
domain?: TypedDataDomain;
types?: Record<string, TypedDataField[] | StarkNetType[]>;
primaryType?: any;
};

Expand Down Expand Up @@ -182,6 +192,12 @@ export type StarknetEIP712VoteMessage = {
metadataUri: string[];
};

export type StarknetEIP712AliasMessage = {
alias: string;
from?: string;
timestamp?: number;
};

export type EIP712ProposeMessage = StarknetEIP712ProposeMessage & {
authenticator: string;
};
Expand Down
Loading

0 comments on commit e088ca6

Please sign in to comment.