Skip to content

Add Delegated Signer

Add a delegated signer to a subaccount, allowing another wallet address to perform authorized actions on behalf of the subaccount. This enables secure delegation of trading operations to automated systems, trading bots, or team members without sharing private keys. Multiple delegated signers can be added to a single subaccount.

Endpoint

POST https://papi.synthetix.io/v1/trade

Request

Request Format

{
  "params": {
    "action": "addDelegatedSigner",
    "subAccountId": "1867542890123456789",
    "walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f89590",
    "permissions": ["trading"]
  },
  "nonce": 1735689600000,
  "signature": {
    "v": 28,
    "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
  }
}

Request Parameters

ParameterTypeRequiredDescription
paramsobjectYesRequest parameters wrapper
params.actionstringYesMust be "addDelegatedSigner"
params.subAccountIdstringYesThe subaccount ID to add the delegated signer to
params.walletAddressstringYesEthereum wallet address of the delegated signer (42-character hex format)
params.permissionsstring[]YesArray of permission levels to grant. Currently supports: ["trading"]
params.expiresAtintegerNoOptional Unix timestamp (milliseconds) when the delegation expires
ParameterTypeRequiredDescription
nonceuint64YesUnix milliseconds timestamp (must be monotonically increasing)
signatureobjectYesEIP-712 signature object
expiresAfteruint64NoUnix milliseconds expiration timestamp (5x rate limit on stale cancels)

EIP-712 Signature Structure

The action object fields are signed using EIP-712. Note that when expiresAt is omitted in the request, it should be set to 0 (not null) in the EIP-712 message:

EIP-712 Type Definitions for Delegation

AddDelegatedSigner

const AddDelegatedSignerTypes = {
  AddDelegatedSigner: [
    { name: "subAccountId", type: "uint256" },
    { name: "walletAddress", type: "address" },
    { name: "permissions", type: "string[]" },
    { name: "expiresAt", type: "uint256" },
    { name: "nonce", type: "uint256" }
  ]
}

GetDelegatedSigners

const GetDelegatedSignersTypes = {
  GetDelegatedSigners: [
    { name: "subAccountId", type: "uint256" },
    { name: "nonce", type: "uint256" }
  ]
}

RemoveDelegatedSigner

const RemoveDelegatedSignerTypes = {
  RemoveDelegatedSigner: [
    { name: "subAccountId", type: "uint256" },
    { name: "walletAddress", type: "address" },
    { name: "nonce", type: "uint256" }
  ]
}

Example Typed Data for AddDelegatedSigner

{
  "types": {
    "EIP712Domain": [
      { "name": "name", "type": "string" },
      { "name": "version", "type": "string" },
      { "name": "chainId", "type": "uint256" },
      { "name": "verifyingContract", "type": "address" }
    ],
    "AddDelegatedSigner": [
      { "name": "subAccountId", "type": "uint256" },
      { "name": "walletAddress", "type": "address" },
      { "name": "permissions", "type": "string[]" },
      { "name": "expiresAt", "type": "uint256" },
      { "name": "nonce", "type": "uint256" }
    ]
  },
  "primaryType": "AddDelegatedSigner",
  "domain": {
    "name": "Synthetix",
    "version": "1",
    "chainId": 1,
    "verifyingContract": "0x0000000000000000000000000000000000000000"
  },
  "message": {
    "subAccountId": "1867542890123456789",
    "walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f89590",
    "permissions": ["trading"],
    "expiresAt": 0,
    "nonce": 1735689600000
  }
}

Important Notes:

  • When expiresAt is not provided or should represent "no expiration", use 0 in the EIP-712 message (not null)
  • The nonce field is included in the EIP-712 message but comes from the top-level request field, not the action object
  • All delegation operations must be signed by the master account owner

Response

Success Response

{
  "status": "ok",
  "response": {
    "subAccountId": "1867542890123456789",
    "walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f89590",
    "permissions": ["trading"],
    "expiresAt": null
  },
  "request_id": "5ccf215d37e3ae6d",
  "timestamp": "2025-01-01T00:00:00Z"
}

Response Fields

FieldTypeDescription
subAccountIdstringThe subaccount ID the signer was added to
walletAddressstringThe delegated signer's wallet address
permissionsstring[]The permission levels granted
expiresAtinteger | nullExpiration timestamp (null if no expiration)

Error Response

{
  "status": "error",
  "error": {
    "message": "Delegated signer already exists",
    "code": "VALIDATION_ERROR"
  },
  "request_id": "5ccf215d37e3ae6d",
  "timestamp": "2025-01-01T00:00:00Z"
}

Common Error Cases

{
  "status": "error",
  "error": {
    "message": "Maximum delegated signers limit reached",
    "code": "VALIDATION_ERROR"
  },
  "request_id": "5ccf215d37e3ae6d",
  "timestamp": "2025-01-01T00:00:00Z"
}
{
  "status": "error",
  "error": {
    "message": "Subaccount not found",
    "code": "NOT_FOUND"
  },
  "request_id": "5ccf215d37e3ae6d",
  "timestamp": "2025-01-01T00:00:00Z"
}
{
  "status": "error",
  "error": {
    "message": "Cannot delegate to self",
    "code": "VALIDATION_ERROR"
  },
  "request_id": "5ccf215d37e3ae6d",
  "timestamp": "2025-01-01T00:00:00Z"
}

Code Examples

Add Trading Bot Signer

{
  "params": {
    "action": "addDelegatedSigner",
    "subAccountId": "1867542890123456789",
    "walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f89590",
    "permissions": ["trading"]
  },
  "nonce": 1735689600000,
  "signature": {
    "v": 28,
    "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
  }
}

Add Team Member for Trading

{
  "params": {
    "action": "addDelegatedSigner",
    "subAccountId": "1867542890123456789",
    "walletAddress": "0x8B3a9A6F8D1e2C4E5B7A9D0F1C3E5A7B9D1F3E5A",
    "permissions": ["trading"]
  },
  "nonce": 1735689600001,
  "signature": {
    "v": 28,
    "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
  }
}

Add Temporary Delegated Signer with Expiration

{
  "params": {
    "action": "addDelegatedSigner",
    "subAccountId": "1867542890123456789",
    "walletAddress": "0x9C4b8E7F0A2D3B6C5E8A1F3D5B7C9E1A3F5D7B9E",
    "permissions": ["trading"],
    "expiresAt": 1767225600000
  },
  "nonce": 1735689600002,
  "signature": {
    "v": 28,
    "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
  }
}

Implementation Notes

  • Master Account Required: Only the master account owner can add delegated signers
  • Multiple Signers: A subaccount can have multiple delegated signers simultaneously
  • Unique Addresses: Each wallet address can only be delegated once per subaccount
  • Trading Permission: The ["trading"] permission grants ability to place, modify, and cancel orders
  • Immediate Effect: Delegations take effect immediately upon successful creation
  • Optional Expiration: Delegations can include an expiration timestamp after which they become invalid
  • Revocable: Delegations can be revoked at any time using the removeDelegatedSigner endpoint
  • Limits: Platform may enforce maximum number of delegated signers per subaccount

Security Considerations

  • No Self-Delegation: Accounts cannot delegate to themselves
  • Address Validation: Wallet addresses must be valid Ethereum addresses
  • Trading Only: Delegated signers can only perform trading operations
  • Signature Required: All delegation operations require valid EIP-712 signatures
  • Master Control: Only the master account retains the ability to manage delegations

Delegate Object Structure

Delegate Object

The delegate object represents a delegated signer in API responses. This differs from the EIP-712 signed structure which includes additional fields like subAccountId and nonce.

FieldTypeDescription
walletAddressstringEthereum wallet address of the delegated signer (42-character hex format)
permissionsstring[]Array of permission levels granted. Currently supports: ["trading"]
expiresAtinteger | nullUnix timestamp (milliseconds) when the delegation expires. null indicates no expiration

Example Delegate Object

{
  "walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f89590",
  "permissions": ["trading"],
  "expiresAt": null
}

Example with Expiration

{
  "walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f89590",
  "permissions": ["trading"],
  "expiresAt": 1767225600000
}

Signing

All trading methods are signed using EIP-712. Each successful trading request will contain:

  • A piece of structured data that includes the sender address
  • A signature of the hash of that structured data, signed by the sender

For detailed information on EIP-712 signing, see EIP-712 Signing.

Nonce Management

The nonce system prevents replay attacks and ensures order uniqueness:

  • Use current timestamp in milliseconds as nonce
  • Each nonce must be greater than the previous one
  • Recommended: Use Date.now() or equivalent
  • If nonce conflicts occur, increment by 1 and retry

Error Handling

ErrorDescription
Invalid signatureEIP-712 signature validation failed
Invalid market symbolMarket symbol not recognized
Nonce already usedNonce must be greater than previous value
Rate limit exceededToo many requests in time window
Request expiredexpiresAfter timestamp has passed