Skip to content

Commit cfed564

Browse files
authored
Merge a40f406 into 581d644
2 parents 581d644 + a40f406 commit cfed564

File tree

34 files changed

+652
-49
lines changed

34 files changed

+652
-49
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ node_modules
1111

1212
# production
1313
*/**/build
14+
*/**/build.zip
1415
*/**/.docusaurus
1516

1617
# misc

@shared/api/helpers/soroban.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { xdr, Address } from "soroban-client";
2+
3+
export function accountIdentifier(account: string) {
4+
return new Address(account).toScVal();
5+
}
6+
7+
// How do we decode these in a more generic way?
8+
export function decodeAccountIdentifier(scVal: Buffer) {
9+
const accountId = xdr.ScVal.fromXDR(scVal);
10+
const val = accountId.value() as xdr.ScObject;
11+
const hyper = val.value() as xdr.Int128Parts;
12+
return hyper.lo().low;
13+
}
14+
15+
export function decodeBytesN(scVal: Buffer) {
16+
const val = xdr.ScVal.fromXDR(scVal);
17+
const scObj = val.value() as xdr.ScObject;
18+
const valBuffer = scObj.value();
19+
return valBuffer.toString();
20+
}
21+
22+
export function decodeScVal(scVal: Buffer) {
23+
const val = xdr.ScVal.fromXDR(scVal);
24+
return val.value()?.toString() || "";
25+
}

@shared/api/internal.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import StellarSdk from "stellar-sdk";
2+
import * as SorobanClient from "soroban-client";
23
import { DataProvider } from "@stellar/wallet-sdk";
34
import {
45
Account,
@@ -21,6 +22,12 @@ import { getIconUrlFromIssuer } from "./helpers/getIconUrlFromIssuer";
2122
import { getDomainFromIssuer } from "./helpers/getDomainFromIssuer";
2223
import { stellarSdkServer } from "./helpers/stellarSdkServer";
2324

25+
import {
26+
decodeAccountIdentifier,
27+
decodeScVal,
28+
decodeBytesN,
29+
} from "./helpers/soroban";
30+
2431
const TRANSACTIONS_LIMIT = 100;
2532

2633
export const createAccount = async (
@@ -175,6 +182,7 @@ export const loadAccount = (): Promise<{
175182
applicationState: APPLICATION_STATE;
176183
allAccounts: Array<Account>;
177184
bipPath: string;
185+
tokenIdList: string[];
178186
}> =>
179187
sendMessageToBackground({
180188
type: SERVICE_TYPES.LOAD_ACCOUNT,
@@ -736,3 +744,124 @@ export const getBlockedDomains = async () => {
736744
});
737745
return resp;
738746
};
747+
748+
type TxToOp = {
749+
[index: string]: {
750+
tx: SorobanClient.Transaction<
751+
SorobanClient.Memo<SorobanClient.MemoType>,
752+
SorobanClient.Operation[]
753+
>;
754+
decoder: (val: Buffer) => string | number;
755+
};
756+
};
757+
758+
interface SorobanTokenRecord {
759+
[key: string]: unknown;
760+
balance: number;
761+
name: string;
762+
symbol: string;
763+
decimals: string;
764+
}
765+
766+
export const getSorobanTokenBalance = async (
767+
server: SorobanClient.Server,
768+
contractId: string,
769+
txBuilders: {
770+
// need a builder per operation until multi-op transactions are released
771+
balance: SorobanClient.TransactionBuilder;
772+
name: SorobanClient.TransactionBuilder;
773+
decimals: SorobanClient.TransactionBuilder;
774+
symbol: SorobanClient.TransactionBuilder;
775+
},
776+
params: SorobanClient.xdr.ScVal[],
777+
) => {
778+
const contract = new SorobanClient.Contract(contractId);
779+
780+
// Right now we can only have 1 operation per TX in Soroban
781+
// There is ongoing work to lift this restriction
782+
// but for now we need to do 4 txs to show 1 user balance. :(
783+
const balanceTx = txBuilders.balance
784+
.addOperation(contract.call("balance", ...params))
785+
.setTimeout(SorobanClient.TimeoutInfinite)
786+
.build();
787+
788+
const nameTx = txBuilders.name
789+
.addOperation(contract.call("name"))
790+
.setTimeout(SorobanClient.TimeoutInfinite)
791+
.build();
792+
793+
const symbolTx = txBuilders.symbol
794+
.addOperation(contract.call("symbol"))
795+
.setTimeout(SorobanClient.TimeoutInfinite)
796+
.build();
797+
798+
const decimalsTx = txBuilders.decimals
799+
.addOperation(contract.call("decimals"))
800+
.setTimeout(SorobanClient.TimeoutInfinite)
801+
.build();
802+
803+
const txs: TxToOp = {
804+
balance: {
805+
tx: balanceTx,
806+
decoder: decodeAccountIdentifier,
807+
},
808+
name: {
809+
tx: nameTx,
810+
decoder: decodeBytesN,
811+
},
812+
symbol: {
813+
tx: symbolTx,
814+
decoder: decodeBytesN,
815+
},
816+
decimals: {
817+
tx: decimalsTx,
818+
decoder: decodeScVal,
819+
},
820+
};
821+
822+
const tokenBalanceInfo = Object.keys(txs).reduce(async (prev, curr) => {
823+
const _prev = await prev;
824+
const { tx, decoder } = txs[curr];
825+
const { results } = await server.simulateTransaction(tx);
826+
if (!results || results.length !== 1) {
827+
throw new Error("Invalid response from simulateTransaction");
828+
}
829+
const result = results[0];
830+
_prev[curr] = decoder(Buffer.from(result.xdr, "base64"));
831+
832+
return _prev;
833+
}, Promise.resolve({} as SorobanTokenRecord));
834+
835+
return tokenBalanceInfo;
836+
};
837+
838+
export const addTokenId = async (
839+
tokenId: string,
840+
): Promise<{
841+
tokenIdList: string[];
842+
}> => {
843+
let error = "";
844+
let tokenIdList = [] as string[];
845+
846+
try {
847+
({ tokenIdList, error } = await sendMessageToBackground({
848+
tokenId,
849+
type: SERVICE_TYPES.ADD_TOKEN_ID,
850+
}));
851+
} catch (e) {
852+
console.error(e);
853+
}
854+
855+
if (error) {
856+
throw new Error(error);
857+
}
858+
859+
return { tokenIdList };
860+
};
861+
862+
export const getTokenIds = async (): Promise<string[]> => {
863+
const resp = await sendMessageToBackground({
864+
type: SERVICE_TYPES.GET_TOKEN_IDS,
865+
});
866+
return resp.tokenIdList;
867+
};

@shared/api/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ import { APPLICATION_STATE } from "../constants/applicationState";
66
import { WalletType } from "../constants/hardwareWallet";
77
import { NetworkDetails } from "../constants/stellar";
88

9+
export enum ActionStatus {
10+
IDLE = "IDLE",
11+
PENDING = "PENDING",
12+
SUCCESS = "SUCCESS",
13+
ERROR = "ERROR",
14+
}
15+
916
export interface Response {
1017
error: string;
1118
messagedId: number;
@@ -48,6 +55,8 @@ export interface Response {
4855
bipPath: string;
4956
blockedDomains: BlockedDomains;
5057
assetDomain: string;
58+
tokenId: string;
59+
tokenIdList: string[];
5160
}
5261

5362
export interface BlockedDomains {
@@ -101,6 +110,16 @@ export interface AssetDomains {
101110

102111
export type Balances = Types.BalanceMap | null;
103112

113+
export interface SorobanBalance {
114+
contractId: string;
115+
total: unknown; // BigNumber
116+
name: string;
117+
symbol: string;
118+
decimals: string;
119+
}
120+
121+
export type TokenBalances = SorobanBalance[];
122+
104123
/* eslint-disable camelcase */
105124
export type HorizonOperation = any;
106125
/* eslint-enable camelcase */

@shared/constants/services.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export enum SERVICE_TYPES {
3333
REMOVE_CUSTOM_NETWORK = "REMOVE_CUSTOM_NETWORK",
3434
EDIT_CUSTOM_NETWORK = "EDIT_CUSTOM_NETWORK",
3535
RESET_EXP_DATA = "RESET_EXP_DATA",
36+
ADD_TOKEN_ID = "ADD_TOKEN_ID",
37+
GET_TOKEN_IDS = "GET_TOKEN_IDS",
3638
}
3739

3840
export enum EXTERNAL_SERVICE_TYPES {

@shared/constants/stellar.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,7 @@ export const DEFAULT_NETWORKS: Array<NetworkDetails> = [
4747
MAINNET_NETWORK_DETAILS,
4848
TESTNET_NETWORK_DETAILS,
4949
];
50+
51+
export const SOROBAN_RPC_URLS = {
52+
futureNet: "https://stellar-futurenet.4d63.com/soroban/rpc",
53+
};

extension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,4 @@
8181
"webextension-polyfill-ts": "^0.19.0",
8282
"yup": "^0.29.1"
8383
}
84-
}
84+
}

extension/public/static/manifest.json

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,13 @@
1010
}
1111
},
1212
"background": {
13-
"scripts": [
14-
"background.min.js"
15-
],
13+
"scripts": ["background.min.js"],
1614
"persistent": true
1715
},
1816
"content_scripts": [
1917
{
20-
"matches": [
21-
"<all_urls>"
22-
],
23-
"js": [
24-
"contentScript.min.js"
25-
],
18+
"matches": ["<all_urls>"],
19+
"js": ["contentScript.min.js"],
2620
"run_at": "document_start"
2721
}
2822
],
@@ -41,8 +35,6 @@
4135
"48": "images/icon48.png",
4236
"128": "images/icon128.png"
4337
},
44-
"permissions": [
45-
"storage"
46-
],
38+
"permissions": ["storage"],
4739
"manifest_version": 2
48-
}
40+
}

extension/src/background/messageListener/popupMessageListener.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
CACHED_BLOCKED_DOMAINS_ID,
3333
NETWORK_ID,
3434
NETWORKS_LIST_ID,
35+
TOKEN_ID_LIST,
3536
} from "constants/localStorageTypes";
3637
import {
3738
FUTURENET_NETWORK_DETAILS,
@@ -568,6 +569,9 @@ export const popupMessageListener = (request: Request) => {
568569
applicationState: (await dataStorageAccess.getItem(APPLICATION_ID)) || "",
569570
allAccounts: allAccountsSelector(currentState),
570571
bipPath: await getBipPath(),
572+
tokenIdList: JSON.parse(
573+
(await dataStorageAccess.getItem(TOKEN_ID_LIST)) || "[]",
574+
),
571575
};
572576
};
573577

@@ -1110,6 +1114,40 @@ export const popupMessageListener = (request: Request) => {
11101114
return {};
11111115
};
11121116

1117+
const addTokenId = async () => {
1118+
const { tokenId } = request;
1119+
const tokenIdList = JSON.parse(
1120+
(await dataStorageAccess.getItem(TOKEN_ID_LIST)) || "{}",
1121+
);
1122+
const keyId = (await dataStorageAccess.getItem(KEY_ID)) || "";
1123+
1124+
const accountTokenIdList = tokenIdList[keyId] || [];
1125+
1126+
if (accountTokenIdList.includes(tokenId)) {
1127+
return { error: "Token ID already exists" };
1128+
}
1129+
1130+
accountTokenIdList.push(tokenId);
1131+
await dataStorageAccess.setItem(
1132+
TOKEN_ID_LIST,
1133+
JSON.stringify({
1134+
...tokenIdList,
1135+
[keyId]: accountTokenIdList,
1136+
}),
1137+
);
1138+
1139+
return { accountTokenIdList };
1140+
};
1141+
1142+
const getTokenIds = async () => {
1143+
const tokenIdList = JSON.parse(
1144+
(await dataStorageAccess.getItem(TOKEN_ID_LIST)) || "{}",
1145+
);
1146+
const keyId = (await dataStorageAccess.getItem(KEY_ID)) || "";
1147+
1148+
return { tokenIdList: tokenIdList[keyId] || [] };
1149+
};
1150+
11131151
const messageResponder: MessageResponder = {
11141152
[SERVICE_TYPES.CREATE_ACCOUNT]: createAccount,
11151153
[SERVICE_TYPES.FUND_ACCOUNT]: fundAccount,
@@ -1145,6 +1183,8 @@ export const popupMessageListener = (request: Request) => {
11451183
[SERVICE_TYPES.CACHE_ASSET_DOMAIN]: cacheAssetDomain,
11461184
[SERVICE_TYPES.GET_BLOCKED_DOMAINS]: getBlockedDomains,
11471185
[SERVICE_TYPES.RESET_EXP_DATA]: resetExperimentalData,
1186+
[SERVICE_TYPES.ADD_TOKEN_ID]: addTokenId,
1187+
[SERVICE_TYPES.GET_TOKEN_IDS]: getTokenIds,
11481188
};
11491189

11501190
return messageResponder[request.type]();

extension/src/constants/localStorageTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export const RECENT_ADDRESSES = "recentAddresses";
1717
export const NETWORK_ID = "network";
1818
export const NETWORKS_LIST_ID = "networksList";
1919
export const METRICS_DATA = "metricsData";
20+
export const TOKEN_ID_LIST = "tokenIdList";

0 commit comments

Comments
 (0)