Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alpha #1256

Merged
merged 33 commits into from
Jul 30, 2024
Merged

Alpha #1256

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7858460
ci: 🎡 Add action to authenticate commits
prashantasdeveloper Jun 5, 2024
290fa75
refactor: 💡 rename secret to MIDDLEWARE_ALLOWED_SIGNERS
prashantasdeveloper Jun 7, 2024
23fbab1
chore: 🤖 use variable instead of secret for allowed signers
prashantasdeveloper Jun 7, 2024
e06ac80
feat: 🎸 Add support for off-chain settlements
prashantasdeveloper Jun 10, 2024
7d50d0f
feat: 🎸 Add method to get allowed signers for a venue
prashantasdeveloper Jun 11, 2024
dc3ae78
feat: 🎸 Add methods to get off chain affirmations
prashantasdeveloper Jun 11, 2024
8845de9
feat: 🎸 Add method to get off chain receipts used by an account
prashantasdeveloper Jun 11, 2024
fc066ad
test: 💍 add test for Account.getOffChainReceipts
prashantasdeveloper Jun 12, 2024
dcc255e
test: 💍 add coverage for Instruction entity
prashantasdeveloper Jun 12, 2024
1948385
test: 💍 add unit test for Venue.getAllowedSigners
prashantasdeveloper Jun 12, 2024
829170f
test: 💍 add unit tests for addInstruction procedure
prashantasdeveloper Jun 12, 2024
17e040c
test: 💍 add unit test for executeManualInstruction procedure
prashantasdeveloper Jun 12, 2024
19bd7c7
test: 💍 Add tests for instruction affimration with receipts
prashantasdeveloper Jun 12, 2024
6a8f1bb
test: 💍 add unit tests for conversion methods
prashantasdeveloper Jun 12, 2024
e648cce
test: 💍 handle FungibleLeg type conversion
prashantasdeveloper Jun 12, 2024
d4e79e9
test: 💍 add unit test for legToOffChainLeg
prashantasdeveloper Jun 12, 2024
1f279b8
feat: 🎸 Add method to generate offchain affirmation receipt
prashantasdeveloper Jun 14, 2024
52c77ee
test: 💍 resolve sonar issue
prashantasdeveloper Jun 14, 2024
b6da75f
feat: 🎸 Add option to add/remove venue signers
prashantasdeveloper Jun 19, 2024
2789470
feat: 🎸 remove updated_at in queries
polymath-eric Jun 21, 2024
c3761c4
feat: 🎸 Use `currentAssetMetadataLocalKey` to get next local ID
prashantasdeveloper May 29, 2024
4920a51
feat: 🎸 Bump supported spec version to 6.3
prashantasdeveloper Jun 20, 2024
7612431
fix: 🐛 get portfolio transactions error
sansan Jun 20, 2024
699fad7
feat: 🎸 allow MultiSig signers to submit proposals
polymath-eric Jun 27, 2024
1af7911
feat: 🎸 add `MultiSig.joinCreator` method
polymath-eric Jul 9, 2024
f72cb68
fix: 🐛 `multiSig.getHistoricalProposal` errors
polymath-eric Jul 11, 2024
8689f63
docs: ✏️ remove @hidden from procedure .multiSig public property
polymath-eric Jul 11, 2024
8f120ca
fix: 🐛 throw catchable connect errors
polymath-eric Jul 15, 2024
e8b1319
docs: ✏️ fix readme type badge link
polymath-eric Jul 16, 2024
5053f00
feat: 🎸 Add procedure to add secondary accounts to an Identity
prashantasdeveloper Jul 18, 2024
8496491
feat: 🎸 Add procedure to create multiple child identities
prashantasdeveloper Jul 19, 2024
dcc13a3
feat: 🎸 Add support for settlements V2
prashantasdeveloper Jul 11, 2024
d0de49c
chore: 🤖 modify queries to work with SQ historical state
prashantasdeveloper Jul 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

{
"parser": "@typescript-eslint/parser",
"env": {
Expand Down Expand Up @@ -101,5 +100,11 @@
"project": "./tsconfig.json"
},
"extends": ["standard", "semistandard", "plugin:@typescript-eslint/recommended", "prettier"],
"ignorePatterns": ["src/polkadot/", "src/middleware/types.ts", "docs/*", "src/sandbox.ts"]
"ignorePatterns": [
"src/polkadot/",
"src/middleware/types.ts",
"src/middleware/typesV6.ts",
"docs/*",
"src/sandbox.ts"
]
}
48 changes: 48 additions & 0 deletions .github/workflows/authenticate-commits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Authenticate Commits
on:
pull_request:
types: [opened, reopened, synchronize]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Import allowed SSH keys
env:
ALLOWED_SIGNERS: ${{ vars.MIDDLEWARE_ALLOWED_SIGNERS }}
run: |
mkdir -p ~/.ssh
echo "$ALLOWED_SIGNERS" > ~/.ssh/allowed_signers
git config --global gpg.ssh.allowedSignersFile "~/.ssh/allowed_signers"

- name: Validate commit signatures
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
# Function to verify a commit
verify_commit() {
local commit=$1
local status=$(git show --pretty="format:%G?" $commit | head -n 1)

if [ "$status" != "G" ]; then
local committer=$(git log -1 --pretty=format:'%cn (%ce)' $commit)
echo "Commit $commit from $committer has an invalid signature or is not signed by an allowed key."
exit 1
fi

}

# Get all commits in the PR
commits=$(git rev-list $BASE_SHA..$HEAD_SHA)

# Iterate over all commits in the PR and verify each one
for COMMIT in $commits; do
verify_commit $COMMIT
done

echo "All commits are signed with allowed keys."
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![js-semistandard-style](https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square)](https://github.com/standard/semistandard)
[![Types](https://img.shields.io/npm/types/@polymeshassociation/polymesh-sdk)](https://)
[![Types](https://img.shields.io/npm/types/@polymeshassociation/polymesh-sdk)](https://www.npmjs.com/package/@polymeshassociation/polymesh-sdk)
[![npm](https://img.shields.io/npm/v/@polymeshassociation/polymesh-sdk)](https://www.npmjs.com/package/@polymeshassociation/polymesh-sdk)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=PolymeshAssociation_polymesh-sdk&metric=coverage)](https://sonarcloud.io/summary/new_code?id=PolymeshAssociation_polymesh-sdk)
Expand Down Expand Up @@ -124,6 +124,29 @@ Creating transactions is a two-step process. First a procedure is created, which
const newAsset = await createAssetProc.run()
```

#### Creating MultiSig Proposals

If the signingAccount is a MultiSig signer, then the transaction will need to be ran with `.runAsProposal()` instead of the usual `.run()`.
The underlying transaction will be wrapped with `multiSig.createProposalAsKey` extrinsic and will resolve to the MultiSig proposal created.

Approving and rejecting existing proposals are an exception and should be submitted with `.run()`. If your application supports
MultiSig signers, then the procedure's `multiSig` param can be checked to ensure the correct method is called.

```typescript
const createAssetProc = await polyClient.assets.createAsset(
args,
{
signingAccount: multiSigSigner
}
)
createAssetProc.multiSig // indicates the acting MultiSig. If set `runAsProposal` must be used
const proposal = await createAssetProc.runAsProposal()

const rejectProc = await proposal.reject({ signingAccount: multiSigSigner })
rejectProc.multiSig // returns `null`. Rejecting a proposal does not get wrapped
await rejectProc.run()
```

#### Reading Data

The SDK exposes getter functions that will return entities, which may have their own functions:
Expand Down
59 changes: 58 additions & 1 deletion src/api/client/AccountManagement.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { hexAddPrefix, hexStripPrefix } from '@polkadot/util';

import { MultiSig } from '~/api/entities/Account/MultiSig';
import {
acceptPrimaryKeyRotation,
Account,
addSecondaryAccounts,
AuthorizationRequest,
Context,
createMultiSigAccount,
Expand All @@ -17,7 +20,9 @@ import {
import {
AcceptPrimaryKeyRotationParams,
AccountBalance,
AddSecondaryAccountsParams,
CreateMultiSigParams,
Identity,
InviteAccountParams,
ModifySignerPermissionsParams,
NoArgsProcedureMethod,
Expand All @@ -28,7 +33,14 @@ import {
SubsidizeAccountParams,
UnsubCallback,
} from '~/types';
import { asAccount, assertAddressValid, createProcedureMethod, getAccount } from '~/utils/internal';
import { bigNumberToU64, dateToMoment, stringToIdentityId } from '~/utils/conversion';
import {
asAccount,
asIdentity,
assertAddressValid,
createProcedureMethod,
getAccount,
} from '~/utils/internal';

/**
* Handles functionality related to Account Management
Expand All @@ -52,6 +64,13 @@ export class AccountManagement {
},
context
);

this.addSecondaryAccounts = createProcedureMethod(
{
getProcedureAndArgs: args => [addSecondaryAccounts, { ...args }],
},
context
);
this.revokePermissions = createProcedureMethod<
{ secondaryAccounts: (string | Account)[] },
ModifySignerPermissionsParams,
Expand Down Expand Up @@ -122,6 +141,13 @@ export class AccountManagement {
*/
public removeSecondaryAccounts: ProcedureMethod<RemoveSecondaryAccountsParams, void>;

/**
* Adds a list of secondary Accounts to the signing Identity
*
* @throws if the signing Account is not the primary Account of the Identity
*/
public addSecondaryAccounts: ProcedureMethod<AddSecondaryAccountsParams, Identity>;

/**
* Revoke all permissions of a list of secondary Accounts associated with the signing Identity
*
Expand Down Expand Up @@ -291,4 +317,35 @@ export class AccountManagement {
* unlinked to any identity.
*/
public acceptPrimaryKey: ProcedureMethod<AcceptPrimaryKeyRotationParams, void>;

/**
* Generate an offchain authorization signature with a specified signer
*
* @param args.signer Signer to be used to generate the off chain auth signature
* @param args.target DID of the identity to which signer is targeting the authorization
* @param args.expiry date after which the authorization expires
*/
public async generateOffChainAuthSignature(args: {
signer: string | Account;
target: string | Identity;
expiry: Date;
}): Promise<`0x${string}`> {
const { context } = this;

const { target, signer, expiry } = args;

const targetIdentity = asIdentity(target, context);

const offChainAuthNonce = await targetIdentity.getOffChainAuthorizationNonce();

const rawTargetDid = stringToIdentityId(targetIdentity.did, context);
const rawNonce = bigNumberToU64(offChainAuthNonce, context);
const rawExpiry = dateToMoment(expiry, context);

const payloadStrings = [rawTargetDid.toHex(), rawNonce.toHex(true), rawExpiry.toHex(true)];

const rawPayload = hexAddPrefix(payloadStrings.map(e => hexStripPrefix(e)).join(''));

return context.getSignature({ rawPayload, signer });
}
}
6 changes: 5 additions & 1 deletion src/api/client/Claims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import {
PolymeshError,
registerCustomClaimType,
} from '~/internal';
import { claimsGroupingQuery, claimsQuery, customClaimTypeQuery } from '~/middleware/queries';
import {
claimsGroupingQuery,
claimsQuery,
customClaimTypeQuery,
} from '~/middleware/queries/claims';
import { ClaimsGroupBy, ClaimsOrderBy, ClaimTypeEnum, Query } from '~/middleware/types';
import { IdentityClaim } from '~/polkadot/polymesh';
import {
Expand Down
22 changes: 22 additions & 0 deletions src/api/client/Identities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
AuthorizationRequest,
ChildIdentity,
Context,
createChildIdentities,
createChildIdentity,
createPortfolios,
Identity,
Expand All @@ -17,6 +18,7 @@ import {
import {
AllowIdentityToCreatePortfoliosParams,
AttestPrimaryKeyRotationParams,
CreateChildIdentitiesParams,
CreateChildIdentityParams,
ProcedureMethod,
RegisterIdentityParams,
Expand Down Expand Up @@ -89,6 +91,13 @@ export class Identities {
context
);

this.createChildren = createProcedureMethod(
{
getProcedureAndArgs: args => [createChildIdentities, args],
},
context
);

this.allowIdentityToCreatePortfolios = createProcedureMethod(
{ getProcedureAndArgs: args => [allowIdentityToCreatePortfolios, args] },
context
Expand Down Expand Up @@ -211,6 +220,19 @@ export class Identities {
*/
public createChild: ProcedureMethod<CreateChildIdentityParams, ChildIdentity>;

/**
* Create child identities using off chain authorization
*
* @note the list of `key` provided in the params should not be linked to any other account
*
* @throws if
* - the signing account is not a primary key
* - the signing Identity is already a child of some other identity
* - `expiresAt` is not a future date
* - the any `key` in `childKeyAuths` is already linked to an Identity
*/
public createChildren: ProcedureMethod<CreateChildIdentitiesParams, ChildIdentity[]>;

/**
* Gives permission to the Identity to create Portfolios on behalf of the signing Identity
*
Expand Down
3 changes: 2 additions & 1 deletion src/api/client/Network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import BigNumber from 'bignumber.js';

import { handleExtrinsicFailure, pollForTransactionFinalization } from '~/base/utils';
import { Account, Context, PolymeshError, transferPolyx } from '~/internal';
import { eventsByArgs, extrinsicByHash } from '~/middleware/queries';
import { eventsByArgs } from '~/middleware/queries/events';
import { extrinsicByHash } from '~/middleware/queries/extrinsics';
import { EventIdEnum, ModuleIdEnum, Query } from '~/middleware/types';
import {
ErrorCode,
Expand Down
9 changes: 3 additions & 6 deletions src/api/client/Polymesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import {
import { signerToString } from '~/utils/conversion';
import {
assertExpectedChainVersion,
assertExpectedSqVersion,
createProcedureMethod,
extractProtocol,
warnUnexpectedSqVersion,
} from '~/utils/internal';

import { AccountManagement } from './AccountManagement';
Expand Down Expand Up @@ -151,8 +151,7 @@ export class Polymesh {

const { metadata, noInitWarn, typesBundle } = polkadot ?? {};

// Defer `await` on any checks to minimize total startup time
const requiredChecks: Promise<void>[] = [assertExpectedChainVersion(nodeUrl)];
await assertExpectedChainVersion(nodeUrl);

try {
const { types, rpc, signedExtensions, runtime } = schema;
Expand Down Expand Up @@ -212,11 +211,9 @@ export class Polymesh {
}
};

requiredChecks.push(checkMiddleware(), assertExpectedSqVersion(context));
await Promise.all([checkMiddleware(), warnUnexpectedSqVersion(context)]);
}

await Promise.all(requiredChecks);

return new Polymesh(context);
}

Expand Down
64 changes: 63 additions & 1 deletion src/api/client/__tests__/AccountManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AccountManagement } from '~/api/client/AccountManagement';
import { Account, MultiSig, PolymeshTransaction, Subsidy } from '~/internal';
import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import { MockContext } from '~/testUtils/mocks/dataSources';
import { AccountBalance, PermissionType, SubCallback } from '~/types';
import { AccountBalance, Identity, PermissionType, SubCallback } from '~/types';
import * as utilsConversionModule from '~/utils/conversion';
import * as utilsInternalModule from '~/utils/internal';

Expand Down Expand Up @@ -396,4 +396,66 @@ describe('AccountManagement class', () => {
expect(tx).toBe(expectedTransaction);
});
});

describe('method: addSecondaryAccounts', () => {
it('should prepare the procedure with the correct arguments and context, and return the resulting transaction', async () => {
const expectedTransaction = 'someTransaction' as unknown as PolymeshTransaction<Identity>;

const args = {
accounts: [
{
secondaryAccount: {
account: entityMockUtils.getAccountInstance({
address: 'secondaryAccount',
getIdentity: null,
}),
permissions: {
assets: null,
portfolios: null,
transactions: null,
transactionGroups: [],
},
},
authSignature: '0xSignature',
},
],
expiresAt: new Date('2050/01/01'),
};

when(procedureMockUtils.getPrepareMock())
.calledWith({ args, transformer: undefined }, context, {})
.mockResolvedValue(expectedTransaction);

const tx = await accountManagement.addSecondaryAccounts(args);

expect(tx).toBe(expectedTransaction);
});
});

describe('generateOffChainAuthSignature', () => {
it('should generate off chain authorization signature for a specific signer targeting an Identity', async () => {
const args = {
signer: entityMockUtils.getAccountInstance({ address: 'signer' }),
target: entityMockUtils.getIdentityInstance({ did: 'someTargetDid' }),
expiry: new Date('2050/01/01'),
};

const rawTargetId = dsMockUtils.createMockIdentityId(args.target.did);
rawTargetId.toHex = jest.fn();
rawTargetId.toHex.mockReturnValue('0x1000000'.padEnd(66, '0'));

const rawNonce = dsMockUtils.createMockU64(new BigNumber(0));
const rawMoment = dsMockUtils.createMockMoment(new BigNumber(args.expiry.getTime()));

jest.spyOn(utilsConversionModule, 'stringToIdentityId').mockReturnValue(rawTargetId);

jest.spyOn(utilsConversionModule, 'bigNumberToU64').mockReturnValue(rawNonce);

jest.spyOn(utilsConversionModule, 'dateToMoment').mockReturnValue(rawMoment);

const result = await accountManagement.generateOffChainAuthSignature(args);

expect(result).toEqual('0xsignature');
});
});
});
Loading
Loading