Skip to content

Commit

Permalink
Revert "Revert "feat: Add Authenticator flow for Permissioned Keys"" (#…
Browse files Browse the repository at this point in the history
…326)

Reverts #325
  • Loading branch information
tyleroooo authored Feb 6, 2025
1 parent fb27e35 commit 1e2842f
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 38 deletions.
2 changes: 2 additions & 0 deletions v4-client-js/examples/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export const DYDX_LOCAL_ADDRESS = 'dydx199tqg4wdlnu4qjlxchpd7seg454937hjrknju4';
export const DYDX_LOCAL_MNEMONIC =
'merge panther lobster crazy road hollow amused security before critic about cliff exhibit cause coyote talent happy where lion river tobacco option coconut small';

export const DYDX_TEST_MNEMONIC_2 = 'movie yard still copper exile wear brisk chest ride dizzy novel future menu finish radar lunar claim hub middle force turtle mouse frequent embark';

export const MARKET_BTC_USD: string = 'BTC-USD';
export const PERPETUAL_PAIR_BTC_USD: number = 0;

Expand Down
110 changes: 110 additions & 0 deletions v4-client-js/examples/permissioned_keys_example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { TextEncoder } from 'util';

import { toBase64 } from '@cosmjs/encoding';
import { Order_TimeInForce } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/clob/order';

import { BECH32_PREFIX } from '../src';
import { CompositeClient } from '../src/clients/composite-client';
import { AuthenticatorType, Network, OrderSide, SelectedGasDenom } from '../src/clients/constants';
import LocalWallet from '../src/clients/modules/local-wallet';
import { SubaccountInfo } from '../src/clients/subaccount';
import { DYDX_TEST_MNEMONIC, DYDX_TEST_MNEMONIC_2 } from './constants';

async function test(): Promise<void> {
const wallet1 = await LocalWallet.fromMnemonic(DYDX_TEST_MNEMONIC, BECH32_PREFIX);
const wallet2 = await LocalWallet.fromMnemonic(DYDX_TEST_MNEMONIC_2, BECH32_PREFIX);

const network = Network.staging();
const client = await CompositeClient.connect(network);
client.setSelectedGasDenom(SelectedGasDenom.NATIVE);

console.log('**Client**');
console.log(client);

const subaccount1 = new SubaccountInfo(wallet1, 0);
const subaccount2 = new SubaccountInfo(wallet2, 0);

// Change second wallet pubkey
// Add an authenticator to allow wallet2 to place orders
console.log("** Adding authenticator **");
await addAuthenticator(client, subaccount1, wallet2.pubKey!.value);

const authenticators = await client.getAuthenticators(wallet1.address!);
// Last element in authenticators array is the most recently created
const lastElement = authenticators.accountAuthenticators.length - 1;
const authenticatorID = authenticators.accountAuthenticators[lastElement].id;

// Placing order using subaccount2 for subaccount1 succeeds
console.log("** Placing order with authenticator **");
await placeOrder(client, subaccount2, subaccount1, authenticatorID);

// Remove authenticator
console.log("** Removing authenticator **");
await removeAuthenticator(client, subaccount1, authenticatorID);

// Placing an order using subaccount2 will now fail
console.log("** Placing order with invalid authenticator should fail **");
await placeOrder(client, subaccount2, subaccount1, authenticatorID);
}

async function removeAuthenticator(client: CompositeClient,subaccount: SubaccountInfo, id: Long): Promise<void> {
await client.removeAuthenticator(subaccount, id);
}

async function addAuthenticator(client: CompositeClient, subaccount: SubaccountInfo, authedPubKey:string): Promise<void> {
const subAuthenticators = [{
type: AuthenticatorType.SIGNATURE_VERIFICATION,
config: authedPubKey,
},
{
type: AuthenticatorType.MESSAGE_FILTER,
config: toBase64(new TextEncoder().encode("/dydxprotocol.clob.MsgPlaceOrder")),
},
];

const jsonString = JSON.stringify(subAuthenticators);
const encodedData = new TextEncoder().encode(jsonString);

await client.addAuthenticator(subaccount, AuthenticatorType.ALL_OF, encodedData);
}

async function placeOrder(client: CompositeClient, fromAccount: SubaccountInfo, forAccount: SubaccountInfo, authenticatorId: Long): Promise<void> {
try {
const side = OrderSide.BUY
const price = Number("1000");
const currentBlock = await client.validatorClient.get.latestBlockHeight();
const nextValidBlockHeight = currentBlock + 5;
const goodTilBlock = nextValidBlockHeight + 10;

const timeInForce = Order_TimeInForce.TIME_IN_FORCE_UNSPECIFIED;

const clientId = Math.floor(Math.random() * 10000);

const tx = await client.placeShortTermOrder(
fromAccount,
'ETH-USD',
side,
price,
0.01,
clientId,
goodTilBlock,
timeInForce,
false,
undefined,
{
authenticators: [authenticatorId],
accountForOrder: forAccount,
}
);
console.log('**Order Tx**');
console.log(Buffer.from(tx.hash).toString('hex'));
} catch (error) {
console.log(error.message);
}
}

test()
.then(() => {})
.catch((error) => {
console.log(error.message);
});
23 changes: 10 additions & 13 deletions v4-client-js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion v4-client-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
"@cosmjs/stargate": "^0.32.1",
"@cosmjs/tendermint-rpc": "^0.32.1",
"@cosmjs/utils": "^0.32.1",
"@dydxprotocol/v4-proto": "8.0.0",
"@osmonauts/lcd": "^0.6.0",
"@dydxprotocol/v4-proto": "7.0.0-dev.0",
"@scure/bip32": "^1.1.5",
"@scure/bip39": "^1.1.1",
"axios": "1.1.3",
Expand Down
43 changes: 40 additions & 3 deletions v4-client-js/src/clients/composite-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
BroadcastTxAsyncResponse,
BroadcastTxSyncResponse,
} from '@cosmjs/tendermint-rpc/build/tendermint37';
import { GetAuthenticatorsResponse } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/accountplus/query';
import {
Order_ConditionType,
Order_TimeInForce,
Expand All @@ -17,6 +18,7 @@ import { bigIntToBytes } from '../lib/helpers';
import { isStatefulOrder, verifyOrderFlags } from '../lib/validation';
import { GovAddNewMarketParams, OrderFlags } from '../types';
import {
AuthenticatorType,
Network,
OrderExecution,
OrderSide,
Expand Down Expand Up @@ -66,6 +68,11 @@ export interface OrderBatchWithMarketId {
clientIds: number[];
}

export interface PermissionedKeysAccountAuth {
authenticators: Long[];
accountForOrder: SubaccountInfo;
}

export class CompositeClient {
public readonly network: Network;
public gasDenom: SelectedGasDenom = SelectedGasDenom.USDC;
Expand Down Expand Up @@ -151,6 +158,7 @@ export class CompositeClient {
memo?: string,
broadcastMode?: BroadcastMode,
account?: () => Promise<Account>,
authenticators?: Long[],
): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
return this.validatorClient.post.send(
wallet,
Expand All @@ -160,6 +168,8 @@ export class CompositeClient {
memo,
broadcastMode,
account,
undefined,
authenticators,
);
}

Expand Down Expand Up @@ -297,10 +307,15 @@ export class CompositeClient {
timeInForce: Order_TimeInForce,
reduceOnly: boolean,
memo?: string,
permissionedKeysAccountAuth?: PermissionedKeysAccountAuth,
): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
// For permissioned orders, use the permissioning account details instead of the subaccount
// This allows placing orders on behalf of another account when using permissioned keys
const accountForOrder = permissionedKeysAccountAuth ? permissionedKeysAccountAuth.accountForOrder : subaccount;
const msgs: Promise<EncodeObject[]> = new Promise((resolve, reject) => {

const msg = this.placeShortTermOrderMessage(
subaccount,
accountForOrder,
marketId,
side,
price,
Expand All @@ -311,14 +326,16 @@ export class CompositeClient {
reduceOnly,
);
msg
.then((it) => resolve([it]))
.then((it) => {
resolve([it]);
})
.catch((err) => {
console.log(err);
reject(err);
});
});
const account: Promise<Account> = this.validatorClient.post.account(
subaccount.address,
accountForOrder.address,
undefined,
);
return this.send(
Expand All @@ -329,6 +346,7 @@ export class CompositeClient {
memo,
undefined,
() => account,
permissionedKeysAccountAuth?.authenticators,
);
}

Expand Down Expand Up @@ -1217,4 +1235,23 @@ export class CompositeClient {
): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
return this.validatorClient.post.createMarketPermissionless(ticker, subaccount, broadcastMode, gasAdjustment, memo);
}

async addAuthenticator(
subaccount: SubaccountInfo,
authenticatorType: AuthenticatorType,
data: Uint8Array,
): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
return this.validatorClient.post.addAuthenticator(subaccount, authenticatorType, data)
}

async removeAuthenticator(
subaccount: SubaccountInfo,
id: Long,
): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
return this.validatorClient.post.removeAuthenticator(subaccount, id)
}

async getAuthenticators(address: string): Promise<GetAuthenticatorsResponse>{
return this.validatorClient.get.getAuthenticators(address);
}
}
17 changes: 16 additions & 1 deletion v4-client-js/src/clients/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ export const TYPE_URL_MSG_UNDELEGATE = '/cosmos.staking.v1beta1.MsgUndelegate';
export const TYPE_URL_MSG_WITHDRAW_DELEGATOR_REWARD =
'/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward';

// x/accountplus
export const TYPE_URL_MSG_ADD_AUTHENTICATOR = '/dydxprotocol.accountplus.MsgAddAuthenticator'
export const TYPE_URL_MSG_REMOVE_AUTHENTICATOR = '/dydxprotocol.accountplus.MsgRemoveAuthenticator'

// ------------ Chain Constants ------------
// The following are same across different networks / deployments.
export const GOV_MODULE_ADDRESS = 'dydx10d07y265gmmuvt4z0w9aw880jnsr700jnmapky';
Expand Down Expand Up @@ -196,6 +200,17 @@ export enum PnlTickInterval {
day = 'day',
}

// ----------- Authenticators -------------

export enum AuthenticatorType {
ALL_OF = 'AllOf',
ANY_OF = 'AnyOf',
SIGNATURE_VERIFICATION = 'SignatureVerification',
MESSAGE_FILTER = 'MessageFilter',
CLOB_PAIR_ID_FILTER = 'ClobPairIdFilter',
SUBACCOUNT_FILTER = 'SubaccountFilter',
}

export enum TradingRewardAggregationPeriod {
DAILY = 'DAILY',
WEEKLY = 'WEEKLY',
Expand Down Expand Up @@ -285,7 +300,7 @@ export class Network {
const indexerConfig = new IndexerConfig(IndexerApiHost.STAGING, IndexerWSHost.STAGING);
const validatorConfig = new ValidatorConfig(
ValidatorApiHost.STAGING,
TESTNET_CHAIN_ID,
STAGING_CHAIN_ID,
{
CHAINTOKEN_DENOM: 'adv4tnt',
USDC_DENOM: 'ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5',
Expand Down
8 changes: 8 additions & 0 deletions v4-client-js/src/clients/lib/registry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GeneratedType, Registry } from '@cosmjs/proto-signing';
import { defaultRegistryTypes } from '@cosmjs/stargate';
import { MsgAddAuthenticator, MsgRemoveAuthenticator } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/accountplus/tx';
import { MsgRegisterAffiliate } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/affiliates/tx';
import {
MsgPlaceOrder,
Expand Down Expand Up @@ -38,6 +39,8 @@ import {
TYPE_URL_MSG_WITHDRAW_FROM_MEGAVAULT,
TYPE_URL_MSG_REGISTER_AFFILIATE,
TYPE_URL_MSG_CREATE_MARKET_PERMISSIONLESS,
TYPE_URL_MSG_ADD_AUTHENTICATOR,
TYPE_URL_MSG_REMOVE_AUTHENTICATOR,
} from '../constants';

export const registry: ReadonlyArray<[string, GeneratedType]> = [];
Expand Down Expand Up @@ -74,6 +77,11 @@ export function generateRegistry(): Registry {
// affiliates
[TYPE_URL_MSG_REGISTER_AFFILIATE, MsgRegisterAffiliate as GeneratedType],


// authentication
[TYPE_URL_MSG_ADD_AUTHENTICATOR, MsgAddAuthenticator as GeneratedType],
[TYPE_URL_MSG_REMOVE_AUTHENTICATOR, MsgRemoveAuthenticator as GeneratedType],

// default types
...defaultRegistryTypes,
]);
Expand Down
Loading

0 comments on commit 1e2842f

Please sign in to comment.