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 <[email protected]>

* 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 <[email protected]>
Co-authored-by: Wiktor Tkaczyński <[email protected]>
3 people authored Jul 29, 2024
1 parent f1e3b13 commit e088ca6
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
@@ -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(
() =>
@@ -28,7 +26,6 @@ const loading = computed(

<template>
<UiButton
v-if="!hidden"
:disabled="loading || isSafeWallet"
class="group"
:class="{ 'hover:border-skin-danger': spaceFollowed }"
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

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

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,
@@ -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();
@@ -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))
1 change: 1 addition & 0 deletions apps/ui/src/networks/index.ts
Original file line number Diff line number Diff line change
@@ -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';
7 changes: 6 additions & 1 deletion apps/ui/src/networks/starknet/actions.ts
Original file line number Diff line number Diff line change
@@ -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
5 changes: 2 additions & 3 deletions apps/ui/src/stores/followedSpaces.ts
Original file line number Diff line number Diff line change
@@ -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;
5 changes: 1 addition & 4 deletions apps/ui/src/views/User.vue
Original file line number Diff line number Diff line change
@@ -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>
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
@@ -21,7 +21,7 @@ import {
weightedVoteTypes
} from './types';
import { offchainGoerli } from '../../../offchainNetworks';
import { OffchainNetworkConfig } from '../../../types';
import { OffchainNetworkConfig, SignatureData } from '../../../types';
import {
CancelProposal,
EIP712CancelProposalMessage,
@@ -38,7 +38,6 @@ import {
FollowSpace,
Propose,
SetAlias,
SignatureData,
UnfollowSpace,
UpdateProposal,
UpdateStatement,
@@ -120,9 +119,10 @@ export class EthereumSig {
signature: sig,
domain,
types,
primaryType,
message
} = envelope.signatureData!;
const payload = {
const payload: any = {
address,
sig,
data: {
@@ -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: {
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
@@ -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' }
@@ -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' }
@@ -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' }
]
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
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,
@@ -60,6 +70,7 @@ export class StarknetSig {
| StarknetEIP712ProposeMessage
| StarknetEIP712UpdateProposalMessage
| StarknetEIP712VoteMessage
| StarknetEIP712AliasMessage
>(
signer: Account,
verifyingContract: string,
@@ -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
};
}
@@ -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
@@ -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';

@@ -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;
};

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

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

export type EIP712ProposeMessage = StarknetEIP712ProposeMessage & {
authenticator: string;
};
Original file line number Diff line number Diff line change
@@ -20,7 +20,13 @@ exports[`StarknetSig > should create propose envelope 1`] = `
],
},
"signatureData": {
"address": "0x7d2f37b75a5e779f7da01c22acee1b66c39e8ba470ee5448f05e1462afcedb4",
"address": "0x07d2f37b75a5e779f7da01c22acee1b66c39e8ba470ee5448f05e1462afcedb4",
"domain": {
"chainId": "0x534e5f5345504f4c4941",
"name": "sx-starknet",
"verifyingContract": "0x213bb25044b189ccfda9882999dba32e011781dc11b2a6efa2b3d232824378e",
"version": "0.1.0",
},
"message": {
"author": "0x7d2f37b75a5e779f7da01c22acee1b66c39e8ba470ee5448f05e1462afcedb4",
"executionStrategy": {
@@ -44,6 +50,62 @@ exports[`StarknetSig > should create propose envelope 1`] = `
"0x7faa90924da8c43edec0109655f719aa06d597a97286d2a4844bc072bffdf3f",
"0x2cd44c86f6f223ca5080c6f6092b93a9c932825b1b6a65e0515debcb22742ea",
],
"types": {
"Propose": [
{
"name": "space",
"type": "ContractAddress",
},
{
"name": "author",
"type": "ContractAddress",
},
{
"name": "metadataUri",
"type": "felt*",
},
{
"name": "executionStrategy",
"type": "Strategy",
},
{
"name": "userProposalValidationParams",
"type": "felt*",
},
{
"name": "salt",
"type": "felt252",
},
],
"StarkNetDomain": [
{
"name": "name",
"type": "felt252",
},
{
"name": "version",
"type": "felt252",
},
{
"name": "chainId",
"type": "felt252",
},
{
"name": "verifyingContract",
"type": "ContractAddress",
},
],
"Strategy": [
{
"name": "address",
"type": "felt252",
},
{
"name": "params",
"type": "felt*",
},
],
},
},
}
`;
@@ -63,7 +125,13 @@ exports[`StarknetSig > should create update proposal envelope 1`] = `
"space": "0x06330d3e48f59f5411c201ee2e9e9ccdc738fb3bb192b0e77e4eda26fa1a22f8",
},
"signatureData": {
"address": "0x7d2f37b75a5e779f7da01c22acee1b66c39e8ba470ee5448f05e1462afcedb4",
"address": "0x07d2f37b75a5e779f7da01c22acee1b66c39e8ba470ee5448f05e1462afcedb4",
"domain": {
"chainId": "0x534e5f5345504f4c4941",
"name": "sx-starknet",
"verifyingContract": "0x213bb25044b189ccfda9882999dba32e011781dc11b2a6efa2b3d232824378e",
"version": "0.1.0",
},
"message": {
"author": "0x7d2f37b75a5e779f7da01c22acee1b66c39e8ba470ee5448f05e1462afcedb4",
"executionStrategy": {
@@ -88,6 +156,72 @@ exports[`StarknetSig > should create update proposal envelope 1`] = `
"0x66f624dd2dcbc81ab4f6aea08db63b902a72517e461a4fbe30c27cd5b775e9b",
"0x28993ed0dd85c5ac3b5017978f4251b014bc7bd375a02048c1e032997f75700",
],
"types": {
"StarkNetDomain": [
{
"name": "name",
"type": "felt252",
},
{
"name": "version",
"type": "felt252",
},
{
"name": "chainId",
"type": "felt252",
},
{
"name": "verifyingContract",
"type": "ContractAddress",
},
],
"Strategy": [
{
"name": "address",
"type": "felt252",
},
{
"name": "params",
"type": "felt*",
},
],
"UpdateProposal": [
{
"name": "space",
"type": "ContractAddress",
},
{
"name": "author",
"type": "ContractAddress",
},
{
"name": "proposalId",
"type": "u256",
},
{
"name": "executionStrategy",
"type": "Strategy",
},
{
"name": "metadataUri",
"type": "felt*",
},
{
"name": "salt",
"type": "felt252",
},
],
"u256": [
{
"name": "low",
"type": "felt252",
},
{
"name": "high",
"type": "felt252",
},
],
},
},
}
`;
@@ -107,7 +241,13 @@ exports[`StarknetSig > should create vote envelope 1`] = `
],
},
"signatureData": {
"address": "0x7d2f37b75a5e779f7da01c22acee1b66c39e8ba470ee5448f05e1462afcedb4",
"address": "0x07d2f37b75a5e779f7da01c22acee1b66c39e8ba470ee5448f05e1462afcedb4",
"domain": {
"chainId": "0x534e5f5345504f4c4941",
"name": "sx-starknet",
"verifyingContract": "0x213bb25044b189ccfda9882999dba32e011781dc11b2a6efa2b3d232824378e",
"version": "0.1.0",
},
"message": {
"choice": "0x1",
"metadataUri": [],
@@ -124,6 +264,72 @@ exports[`StarknetSig > should create vote envelope 1`] = `
"0x155b32bbfbb261e29f97dfae43b5423f1a205eb1ef26680e7edf1758453d63a",
"0x5da6c03b40c281be8686afd2d985db93fc3fc27e4c8a2856ba7c635ebf1b2d6",
],
"types": {
"IndexedStrategy": [
{
"name": "index",
"type": "felt252",
},
{
"name": "params",
"type": "felt*",
},
],
"StarkNetDomain": [
{
"name": "name",
"type": "felt252",
},
{
"name": "version",
"type": "felt252",
},
{
"name": "chainId",
"type": "felt252",
},
{
"name": "verifyingContract",
"type": "ContractAddress",
},
],
"Vote": [
{
"name": "space",
"type": "ContractAddress",
},
{
"name": "voter",
"type": "ContractAddress",
},
{
"name": "proposalId",
"type": "u256",
},
{
"name": "choice",
"type": "felt252",
},
{
"name": "userVotingStrategies",
"type": "IndexedStrategy*",
},
{
"name": "metadataUri",
"type": "felt*",
},
],
"u256": [
{
"name": "low",
"type": "felt252",
},
{
"name": "high",
"type": "felt252",
},
],
},
},
}
`;

0 comments on commit e088ca6

Please sign in to comment.