Skip to content

Commit

Permalink
feat: sdk-accounts linked accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
voliva committed Jan 13, 2025
1 parent 284a3ee commit 089c32c
Show file tree
Hide file tree
Showing 11 changed files with 393 additions and 1 deletion.
5 changes: 5 additions & 0 deletions packages/sdk-accounts/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## Unreleased

Initial release
1 change: 1 addition & 0 deletions packages/sdk-accounts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @polkadot-api/sdk-accounts
57 changes: 57 additions & 0 deletions packages/sdk-accounts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "@polkadot-api/sdk-accounts",
"version": "0.0.1-0",
"sideEffects": false,
"author": "Victor Oliva (https://github.com/voliva)",
"repository": {
"type": "git",
"url": "git+https://github.com/polkadot-api/papi-sdks.git"
},
"exports": {
".": {
"node": {
"production": {
"import": "./dist/esm/index.mjs",
"require": "./dist/min/index.js",
"default": "./dist/index.js"
},
"import": "./dist/esm/index.mjs",
"require": "./dist/index.js",
"default": "./dist/index.js"
},
"module": "./dist/esm/index.mjs",
"import": "./dist/esm/index.mjs",
"require": "./dist/index.js",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"main": "./dist/index.js",
"module": "./dist/esm/index.mjs",
"browser": "./dist/esm/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsc --noEmit && rollup -c ../../rollup.config.js",
"lint": "prettier --check README.md \"src/**/*.{js,jsx,ts,tsx,json,md}\"",
"format": "prettier --write README.md \"src/**/*.{js,jsx,ts,tsx,json,md}\"",
"prepack": "pnpm run build"
},
"license": "MIT",
"dependencies": {
"@polkadot-api/common-sdk-utils": "workspace:*",
"@react-rxjs/utils": "^0.9.7"
},
"peerDependencies": {
"@noble/hashes": ">=1.6.1",
"polkadot-api": ">=1.8.1",
"rxjs": ">=7.8.0"
},
"devDependencies": {
"@noble/hashes": "^1.6.1",
"polkadot-api": "^1.8.1",
"rxjs": "^7.8.1"
}
}
4 changes: 4 additions & 0 deletions packages/sdk-accounts/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { createLinkedAccountsSdk } from "./linkedAccounts/linked-accounts-sdk"
export { novasamaProvider } from "./linkedAccounts/novasama-provider"
export type * from "./linkedAccounts/descriptors"
export type * from "./linkedAccounts/sdk-types"
60 changes: 60 additions & 0 deletions packages/sdk-accounts/src/linkedAccounts/descriptors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
ApisTypedef,
Enum,
PalletsTypedef,
SS58String,
StorageDescriptor,
TypedApi,
} from "polkadot-api"

type ProxyType = Enum<{
Any: undefined
NonTransfer: undefined
Governance: undefined
Staking: undefined
CancelProxy: undefined
Auction: undefined
NominationPools: undefined
}>

type LinkedAccountsSdkPallets = PalletsTypedef<
{
Proxy: {
/**
* The set of account proxies. Maps the account which has delegated to the accounts
* which are being delegated to, together with the amount held on deposit.
*/
Proxies: StorageDescriptor<
[Key: SS58String],
[
Array<{
delegate: SS58String
proxy_type: ProxyType
delay: number
}>,
unknown,
],
false,
never
>
}
},
{},
{},
{},
{}
>
type LinkedAccountsSdkDefinition = SdkDefinition<
LinkedAccountsSdkPallets,
ApisTypedef<{}>
>
export type LinkedAccountsSdkTypedApi = TypedApi<LinkedAccountsSdkDefinition>

type SdkDefinition<P, R> = {
descriptors: Promise<any> & {
pallets: P
apis: R
}
asset: any
metadataTypes: any
}
114 changes: 114 additions & 0 deletions packages/sdk-accounts/src/linkedAccounts/linked-accounts-sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { SS58String } from "polkadot-api"
import {
combineLatest,
defaultIfEmpty,
filter,
from,
map,
merge,
Observable,
of,
shareReplay,
startWith,
switchMap,
take,
} from "rxjs"
import { LinkedAccountsSdkTypedApi } from "./descriptors"
import {
LinkedAccountsResult,
LinkedAccountsSdk,
MultisigProvider,
NestedLinkedAccountsResult,
} from "./sdk-types"

export function createLinkedAccountsSdk(
typedApi: LinkedAccountsSdkTypedApi,
multisigProvider: MultisigProvider,
): LinkedAccountsSdk {
const proxy$ = (address: string) =>
from(typedApi.query.Proxy.Proxies.getValue(address)).pipe(
map((r) => r[0].map((v) => v.delegate)),
)

const cache: Record<SS58String, Observable<LinkedAccountsResult>> = {}
const getLinkedAccounts$ = (address: SS58String) => {
if (address in cache) return cache[address]
cache[address] = merge(
proxy$(address).pipe(
filter((v) => v.length > 0),
map(
(value): LinkedAccountsResult => ({
type: "proxy",
value: { addresses: value },
}),
),
),
from(multisigProvider(address)).pipe(
filter((v) => !!v),
map((value): LinkedAccountsResult => ({ type: "multisig", value })),
),
).pipe(
take(1),
defaultIfEmpty({
type: "root",
} satisfies LinkedAccountsResult),
shareReplay(1),
)
return cache[address]
}

const getNestedLinkedAccounts$ = (
address: SS58String,
): Observable<NestedLinkedAccountsResult> =>
getLinkedAccounts$(address).pipe(
switchMap((result) => {
if (result.type === "root") return of(result)

const accounts$ = combineLatest(
result.value.addresses.map((inner) =>
getNestedLinkedAccounts$(inner),
),
).pipe(
map((nested) =>
result.value.addresses.map((inner, i) => ({
address: inner,
linkedAccounts: nested[i],
})),
),
startWith(
result.value.addresses.map((inner) => ({
address: inner,
linkedAccounts: null,
})),
),
)

if (result.type === "proxy") {
return accounts$.pipe(
map(
(accounts): NestedLinkedAccountsResult => ({
type: "proxy",
value: { accounts },
}),
),
)
}
return accounts$.pipe(
map(
(accounts): NestedLinkedAccountsResult => ({
type: "multisig",
value: {
threshold: result.value.threshold,
accounts,
},
}),
),
)
}),
)

return {
getLinkedAccounts$,
getNestedLinkedAccounts$,
}
}
64 changes: 64 additions & 0 deletions packages/sdk-accounts/src/linkedAccounts/novasama-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { AccountId, Binary, getSs58AddressInfo } from "polkadot-api"
import { MultisigProvider } from "./sdk-types"

export const novasamaProvider: MultisigProvider = async (account) => {
try {
const info = getSs58AddressInfo(account)
if (!info.isValid) {
throw new Error("Invalid SS58")
}
const accountIdCodec = AccountId(info.ss58Format)

const response = await fetch(
"https://subquery-proxy-polkadot-prod.novasama-tech.org",
{
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
body: JSON.stringify({
query: `
query Multisig($address: String) {
accounts(filter: {id: {equalTo: $address}, isMultisig: {equalTo: true}}, first: 1) {
nodes {
signatories {
nodes {
signatoryId
}
}
threshold
}
}
}
`,
variables: {
address: Binary.fromBytes(info.publicKey).asHex(),
},
operationName: "Multisig",
}),
},
)
const result = await (response.json() as Promise<{
data: {
accounts: {
nodes: Array<{
signatories: { nodes: Array<{ signatoryId: string }> }
threshold: number
}>
}
}
}>)
const multisig = result.data.accounts.nodes[0]
if (!multisig) return null
return {
threshold: multisig.threshold,
addresses: multisig.signatories.nodes.map((v) =>
accountIdCodec.dec(v.signatoryId),
),
}
} catch (ex) {
console.error(ex)
return null
}
}
56 changes: 56 additions & 0 deletions packages/sdk-accounts/src/linkedAccounts/sdk-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { SS58String } from "polkadot-api"
import { Observable } from "rxjs"

export type LinkedAccountsResult =
| {
type: "root"
}
| {
type: "proxy"
value: {
addresses: SS58String[]
}
}
| {
type: "multisig"
value: {
threshold: number
addresses: SS58String[]
}
}

export type NestedLinkedAccountsResult =
| {
type: "root"
}
| {
type: "proxy"
value: {
accounts: Array<{
address: SS58String
linkedAccounts: NestedLinkedAccountsResult | null
}>
}
}
| {
type: "multisig"
value: {
threshold: number
accounts: Array<{
address: SS58String
linkedAccounts: NestedLinkedAccountsResult | null
}>
}
}

export interface LinkedAccountsSdk {
getLinkedAccounts$: (address: SS58String) => Observable<LinkedAccountsResult>
getNestedLinkedAccounts$: (
address: SS58String,
) => Observable<NestedLinkedAccountsResult>
}

export type MultisigProvider = (address: SS58String) => Promise<{
addresses: SS58String[]
threshold: number
} | null>
12 changes: 12 additions & 0 deletions packages/sdk-accounts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base",
"include": ["src", "tests"],
"compilerOptions": {
"baseUrl": "src",
"resolveJsonModule": true,
"skipLibCheck": true,
"paths": {
"@/*": ["*"]
}
}
}
2 changes: 1 addition & 1 deletion packages/sdk-governance/README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# @polkadot-api/ink-sdk
# @polkadot-api/sdk-governance
Loading

0 comments on commit 089c32c

Please sign in to comment.