@d8x/perpetuals-sdk
Version:
Node TypeScript SDK for D8X Perpetual Futures
245 lines • 10.2 kB
JavaScript
import { AbiCoder, JsonRpcProvider, keccak256, verifyMessage, verifyTypedData, Wallet, } from "ethers";
import { referralDomain, referralTypes, } from "./nodeSDKTypes";
/**
* This is a 'standalone' class that deals with signatures
* required for referral codes:
* - referrer creates a new referral code for trader (no agency involved)
* - agency creates a new referral code for a referrer and their trader
* - trader selects a referral code to trade with
*
* Note that since the back-end is chain specific, the referral code is typically bound to
* one chain, unless the backend employs code transferrals
*/
export default class ReferralCodeSigner {
constructor(signer, address, _rpcURL) {
this.rpcURL = _rpcURL;
this.address = address;
if (typeof signer == "string") {
const wallet = this.createSignerInstance(signer);
this.signingFun = (x) => wallet.signMessage(x);
this.signingTypedDataFun = (domain, types, value) => wallet.signTypedData(domain, types, value);
}
else if ("signMessage" in signer) {
this.signingFun = (x) => signer.signMessage(x);
this.signingTypedDataFun = (domain, types, value) => signer.signTypedData(domain, types, value);
}
else if (signer.length === 1) {
this.signingFun = signer;
}
else {
this.signingTypedDataFun = signer;
}
}
createSignerInstance(_privateKey) {
this.provider = new JsonRpcProvider(this.rpcURL);
const wallet = new Wallet(_privateKey);
wallet.connect(this.provider);
this.signingFun = (x) => wallet.signMessage(x);
this.signingTypedDataFun = (domain, types, value) => wallet.signTypedData(domain, types, value);
return wallet;
}
async getSignatureForNewReferral(rp) {
if (this.signingTypedDataFun != undefined) {
return await this.signingTypedDataFun(referralDomain, {
NewReferral: [...referralTypes.NewReferral],
}, ReferralCodeSigner.newReferralPayloadToTypedData(rp));
}
else if (this.signingFun != undefined) {
return await ReferralCodeSigner.getSignatureForNewReferral(rp, this.signingFun);
}
else {
throw Error("no signer defined, call createSignerInstance()");
}
}
async getSignatureForNewCode(rc) {
if (this.signingTypedDataFun != undefined) {
return await this.signingTypedDataFun(referralDomain, {
NewCode: [...referralTypes.NewCode],
}, ReferralCodeSigner.referralCodeNewCodePayloadToTypedData(rc));
}
else if (this.signingFun != undefined) {
return await ReferralCodeSigner.getSignatureForNewCode(rc, this.signingFun);
}
else {
throw Error("no signer defined, call createSignerInstance()");
}
}
async getSignatureForCodeSelection(rc) {
if (this.signingTypedDataFun != undefined) {
return await this.signingTypedDataFun(referralDomain, {
CodeSelection: [...referralTypes.CodeSelection],
}, ReferralCodeSigner.codeSelectionPayloadToTypedData(rc));
}
else if (this.signingFun != undefined) {
return await ReferralCodeSigner.getSignatureForCodeSelection(rc, this.signingFun);
}
else {
throw Error("no signer defined, call createSignerInstance()");
}
}
async getAddress() {
if (this.signingFun == undefined) {
throw Error("no signer defined, call createSignerInstance()");
}
return this.address;
}
/**
* New agency/broker to agency referral
* rc.PassOnPercTDF must be in 100*percentage unit
* @param rc payload to sign
* @param signingFun signing function
* @returns signature
*/
static async getSignatureForNewReferral(rp, signingFun) {
if (Math.abs(rp.passOnPercTDF - Math.round(rp.passOnPercTDF)) > 1e-4) {
throw Error("PassOnPercTDF must be in 100*percentage unit, e.g., 2.25% -> 225");
}
let digest = ReferralCodeSigner._referralNewToMessage(rp);
let digestBuffer = Buffer.from(digest.substring(2, digest.length), "hex");
return await signingFun(digestBuffer);
}
/**
* New code
* rc.PassOnPercTDF must be in 100*percentage unit
* @param rc APIReferralCodePayload without signature
* @param signingFun function that signs
* @returns signature string
*/
static async getSignatureForNewCode(rc, signingFun) {
if (Math.abs(rc.passOnPercTDF - Math.round(rc.passOnPercTDF)) > 1e-4) {
throw Error("PassOnPercTDF must be in 100*percentage unit, e.g., 2.25% -> 225");
}
let digest = ReferralCodeSigner._referralCodeNewCodePayloadToMessage(rc);
let digestBuffer = Buffer.from(digest.substring(2, digest.length), "hex");
return await signingFun(digestBuffer);
}
static async getSignatureForCodeSelection(rc, signingFun) {
let digest = ReferralCodeSigner._codeSelectionPayloadToMessage(rc);
let digestBuffer = Buffer.from(digest.substring(2, digest.length), "hex");
return await signingFun(digestBuffer);
}
static _referralNewToMessage(rc) {
let abiCoder = new AbiCoder();
const passOnPercTwoDigitsFormat = Math.round(rc.passOnPercTDF);
let digest = keccak256(abiCoder.encode(["address", "address", "uint32", "uint256"], [rc.parentAddr, rc.referToAddr, passOnPercTwoDigitsFormat, Math.round(rc.createdOn)]));
return digest;
}
/**
* Convert payload to data struct to sign
* @param rc payload
* @returns typed data
*/
static newReferralPayloadToTypedData(rc) {
return {
ParentAddr: rc.parentAddr,
ReferToAddr: rc.referToAddr,
PassOnPercTDF: Math.round(rc.passOnPercTDF),
CreatedOn: BigInt(Math.round(rc.createdOn)),
};
}
/**
* Create digest for referralCodePayload that is to be signed
* @param rc payload
* @returns the hex-string to be signed
*/
static _referralCodeNewCodePayloadToMessage(rc) {
let abiCoder = new AbiCoder();
const passOnPercTwoDigitsFormat = Math.round(rc.passOnPercTDF);
let digest = keccak256(abiCoder.encode(["string", "address", "uint32", "uint256"], [rc.code, rc.referrerAddr, passOnPercTwoDigitsFormat, Math.round(rc.createdOn)]));
return digest;
}
/**
* Convert payload to data struct to sign
* @param rc payload
* @returns typed data
*/
static referralCodeNewCodePayloadToTypedData(rc) {
return {
Code: rc.code,
ReferrerAddr: rc.referrerAddr,
PassOnPercTDF: Math.round(rc.passOnPercTDF),
CreatedOn: BigInt(Math.round(rc.createdOn)),
};
}
/**
* Create digest for APIReferralCodeSelectionPayload that is to be signed
* @param rc payload
* @returns the hex-string to be signed
*/
static _codeSelectionPayloadToMessage(rc) {
let abiCoder = new AbiCoder();
let digest = keccak256(abiCoder.encode(["string", "address", "uint256"], [rc.code, rc.traderAddr, Math.round(rc.createdOn)]));
return digest;
}
/**
* Convert payload to data struct to sign
* @param rc payload
* @returns typed data
*/
static codeSelectionPayloadToTypedData(rc) {
return {
Code: rc.code,
TraderAddr: rc.traderAddr,
CreatedOn: BigInt(Math.round(rc.createdOn)),
};
}
/**
* Check whether signature is correct on payload:
* - the referrer always signs
* - if the agency is not an agency for this referrer, the backend will reject
* @param rc referralcode payload with a signature
* @returns true if correctly signed, false otherwise
*/
static checkNewCodeSignature(rc) {
if (rc.signature == undefined || rc.signature == "") {
return false;
}
try {
// typed-data (^2.x.x)
const typedData = ReferralCodeSigner.referralCodeNewCodePayloadToTypedData(rc);
const signerAddress = verifyTypedData(referralDomain, { NewCode: [...referralTypes.NewCode] }, typedData, rc.signature);
if (rc.referrerAddr.toLowerCase() == signerAddress.toLowerCase()) {
return true;
}
}
catch (err) {
console.log("invalid eip-712 signature:", err);
}
// digest (1.x.x)
try {
let digest = ReferralCodeSigner._referralCodeNewCodePayloadToMessage(rc);
let digestBuffer = Buffer.from(digest.substring(2, digest.length), "hex");
const signerAddress = verifyMessage(digestBuffer, rc.signature);
return rc.referrerAddr.toLowerCase() == signerAddress.toLowerCase();
}
catch (err) {
console.log("invalid eip-191 signature:", err);
}
return false;
}
static checkCodeSelectionSignature(rc) {
if (rc.signature == undefined || rc.signature == "") {
return false;
}
try {
// typed-data (^2.x.x)
const typedData = ReferralCodeSigner.codeSelectionPayloadToTypedData(rc);
const signerAddress = verifyTypedData(referralDomain, { CodeSelection: [...referralTypes.CodeSelection] }, typedData, rc.signature);
return rc.traderAddr.toLowerCase() == signerAddress.toLowerCase();
}
catch (err) {
console.log(err);
// digest (1.x.x)
try {
let digest = ReferralCodeSigner._codeSelectionPayloadToMessage(rc);
let digestBuffer = Buffer.from(digest.substring(2, digest.length), "hex");
const signerAddress = verifyMessage(digestBuffer, rc.signature);
return rc.traderAddr.toLowerCase() == signerAddress.toLowerCase();
}
catch (err) {
return false;
}
}
}
}
//# sourceMappingURL=referralCodeSigner.js.map