Skip to content

New Proposal<AccessSubIdentity> and Proposal<Borrow> TS Bindings #1693

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 9 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ exclude = ["bindings/wasm/identity_wasm", "bindings/grpc"]

[workspace.dependencies]
bls12_381_plus = { version = "0.8.17" }
iota_interaction = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.2.1", package = "iota_interaction" }
iota_interaction_ts = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.2.1", package = "iota_interaction_ts" }
product_common = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.2.1", package = "product_common" }
iota_interaction = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.6.1", package = "iota_interaction" }
iota_interaction_ts = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.6.1", package = "iota_interaction_ts" }
product_common = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.6.1", package = "product_common" }
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
serde_json = { version = "1.0", default-features = false }
strum = { version = "0.25", default-features = false, features = ["std", "derive"] }
Expand All @@ -50,3 +50,9 @@ opt-level = 's'
# If not, remove the next line
# If yes, describe the helping effect in the comment above
# debug = true

# [patch."https://github.com/iotaledger/product-core.git"]
# iota_interaction = { path = "../product-core/iota_interaction" }
# iota_interaction_ts = { path = "../product-core/bindings/wasm/iota_interaction_ts" }
# iota_interaction_rust = { path = "../product-core/iota_interaction_rust" }
# product_common = { path = "../product-core/product_common" }
2 changes: 1 addition & 1 deletion bindings/grpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ identity_iota = { path = "../../identity_iota", features = ["resolver", "sd-jwt"
identity_jose = { path = "../../identity_jose" }
identity_storage = { path = "../../identity_storage", features = ["memstore"] }
identity_stronghold = { path = "../../identity_stronghold", features = ["send-sync-storage"] }
iota-sdk = { git = "https://github.com/iotaledger/iota.git", package = "iota-sdk", tag = "v0.12.0-rc" }
iota-sdk = { git = "https://github.com/iotaledger/iota.git", package = "iota-sdk", tag = "v1.2.3" }
iota-sdk-legacy = { package = "iota-sdk", version = "1.1.2", features = ["stronghold"] }
prost = "0.13"
rand = "0.8.5"
Expand Down
13 changes: 9 additions & 4 deletions bindings/wasm/identity_wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@ identity_ecdsa_verifier = { path = "../../../identity_ecdsa_verifier", default-f
identity_eddsa_verifier = { path = "../../../identity_eddsa_verifier", default-features = false, features = ["ed25519"] }
# Remove iota-sdk dependency while working on issue #1445
iota-sdk = { version = "1.1.5", default-features = false, features = ["serde", "std"] }
iota_interaction = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.2.1", package = "iota_interaction", default-features = false }
iota_interaction_ts = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.2.1", package = "iota_interaction_ts" }
iota_interaction = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.6.1", package = "iota_interaction", default-features = false }
iota_interaction_ts = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.6.1", package = "iota_interaction_ts" }
js-sys = { version = "0.3.61" }
json-proof-token = "0.3.4"
proc_typescript = { version = "0.1.0", path = "./proc_typescript" }
product_common = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.2.1", package = "product_common", features = ["core-client", "transaction", "bindings"] }
product_common = { git = "https://github.com/iotaledger/product-core.git", tag = "v0.6.1", package = "product_common", default-features = false, features = ["core-client", "transaction", "bindings"] }
secret-storage = { git = "https://github.com/iotaledger/secret-storage.git", default-features = false, tag = "v0.3.0" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6.5"
serde_json = { version = "1.0", default-features = false }
serde_repr = { version = "0.1", default-features = false }
# Want to use the nice API of tokio::sync::RwLock for now even though we can't use threads.
tokio = { version = "1.43", default-features = false, features = ["sync"] }
tokio = { version = "1.44", default-features = false, features = ["sync"] }
tsify = "0.4.5"
wasm-bindgen = { version = "0.2.100", features = ["serde-serialize"] }
wasm-bindgen-futures = { version = "0.4", default-features = false }
Expand Down Expand Up @@ -82,3 +82,8 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test
[features]
default = []
keytool = ["iota_interaction_ts/keytool", "identity_iota/keytool"]

[patch."https://github.com/iotaledger/product-core.git"]
iota_interaction = { path = "../../../../product-core/iota_interaction" }
iota_interaction_ts = { path = "../../../../product-core/bindings/wasm/iota_interaction_ts" }
product_common = { path = "../../../../product-core/product_common", default-features = false, features = ["core-client", "transaction", "bindings"] }
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2020-2025 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { IotaDocument } from "@iota/identity-wasm/node";
import { IotaClient } from "@iota/iota-sdk/client";
import { getFundedClient, getMemstorage, NETWORK_URL } from "../util";

/** Demonstrate how an IOTA Identity can own another IOTA Identity. */
export async function didOwnsDid(): Promise<void> {
Error.stackTraceLimit = Infinity;
// create new client to connect to IOTA network
const iotaClient = new IotaClient({ url: NETWORK_URL });
const network = await iotaClient.getChainIdentifier();

// create new client that offers identity related functions
const storage = getMemstorage();
const identityClient = await getFundedClient(storage);

const { output: identity } = await identityClient
.createIdentity(new IotaDocument(network))
.finish()
.buildAndExecute(identityClient);
const identityDid = identity.didDocument().id();

console.log(`Created Identity \`${identityDid}\``);

// create another identity owned by the previous one.
const { output: subIdentity } = await identityClient
.createIdentity(new IotaDocument(network))
.controller(identity.id(), 1n)
.finish()
.buildAndExecute(identityClient);
const subIdentityDid = subIdentity.didDocument().id();

console.log(`Created Identity \`${subIdentityDid}\` owned by Identity \`${identityDid}\``);

// controllers of `identity` can access `subIdentity` in `identity`'s stead.
const identityToken = await identity.getControllerToken(identityClient);
if (!identityToken) {
throw new Error(
`address \`${identityClient.senderAddress()}\` has no control over Identity \`${identityDid}\``,
);
}
const { output } = await identity
.accessSubIdentity(
identityToken,
subIdentity,
async (identity, token) => identity.deactivateDid(token).transaction,
)
.buildAndExecute(identityClient);

console.assert(subIdentity.didDocument().metadata().deactivated, "Whooops, sub identity wasn't updated");
}
3 changes: 3 additions & 0 deletions bindings/wasm/identity_wasm/examples/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { deleteIdentityDID } from "./0_basic/4_delete_did";
import { createVC } from "./0_basic/5_create_vc";
import { createVP } from "./0_basic/6_create_vp";
import { revokeVC } from "./0_basic/7_revoke_vc";
import { didOwnsDid } from "./1_advanced/0_did_owns_did";
import { sdJwtVc } from "./1_advanced/10_sd_jwt_vc";
import { advancedTransaction } from "./1_advanced/11_advanced_transactions";
import { iotaKeytoolIntegration } from "./1_advanced/12_iota_keytool_integration";
Expand Down Expand Up @@ -61,6 +62,8 @@ export async function main(example?: string) {
return await advancedTransaction();
case "12_iota_keytool_integration":
return await iotaKeytoolIntegration();
case "0_did_owns_did":
return await didOwnsDid();
default:
throw "Unknown example name: '" + argument + "'";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { TransactionBuilder } from "@iota/identity-wasm/node";
import { IotaClient } from "@iota/iota-sdk/client";
import { Argument } from "@iota/iota-sdk/transactions";
import { createDocumentForNetwork, getFundedClient, getMemstorage, NETWORK_URL, SendZeroCoinTx } from "../util";

async function testIdentityBorrow(): Promise<void> {
// create new client to connect to IOTA network
const iotaClient = new IotaClient({ url: NETWORK_URL });
const network = await iotaClient.getChainIdentifier();

// create new client that offers identity related functions
const storage = getMemstorage();
const identityClient = await getFundedClient(storage);

// create new unpublished document
const [unpublished] = await createDocumentForNetwork(storage, network);

const { output: identity } = await identityClient
.createIdentity(unpublished)
.finish()
.buildAndExecute(identityClient);
// Get this address's auth token over identity.
const controllerToken = await identity.getControllerToken(identityClient);

// Give identity an empty coin.
const { output: coinId } = await new TransactionBuilder(new SendZeroCoinTx(identity.id()))
.buildAndExecute(identityClient);

console.log(`identity ${identity.id()} now owns the coin ${coinId}`);

// Borrow identity's coin in a transaction. This one doesn't do anything meaningful
// but demonstrate that borrowing works.
const { output } = await identity
.borrowAssets(
controllerToken!,
[coinId],
(ptb, objs) => {
// Let's get the tx's argument corresponding to the coin we borrowed.
const [coinArg, _] = objs.get(coinId) ?? [undefined, undefined];
// Let's call a function that uses it as its argument.
ptb.commands.push({
$kind: "MoveCall",
MoveCall: {
package: "0x2",
module: "coin",
function: "value",
typeArguments: ["0x2::iota::IOTA"],
arguments: [coinArg!],
},
});
},
)
.buildAndExecute(identityClient);

if (output != undefined) {
throw new Error("Transaction failed!");
}
}

// Only verifies that no uncaught exceptions are thrown, including syntax errors etc.
describe("Test node examples", function() {
it("Identity Borrow Asset Proposal", async () => {
await testIdentityBorrow();
});
});
30 changes: 29 additions & 1 deletion bindings/wasm/identity_wasm/examples/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ import {
MethodScope,
Storage,
StorageSigner,
Transaction,
} from "@iota/identity-wasm/node";
import { IotaClient } from "@iota/iota-sdk/client";
import { CoreClientReadOnly } from "@iota/iota-interaction-ts/node/core_client";
import { IotaClient, TransactionEffects } from "@iota/iota-sdk/client";
import { getFaucetHost, requestIotaFromFaucetV0 } from "@iota/iota-sdk/faucet";
import { Transaction as SdkTransaction } from "@iota/iota-sdk/transactions";

export const IOTA_IDENTITY_PKG_ID = globalThis?.process?.env?.IOTA_IDENTITY_PKG_ID || "";
export const NETWORK_NAME_FAUCET = globalThis?.process?.env?.NETWORK_NAME_FAUCET || "localnet";
Expand Down Expand Up @@ -86,3 +89,28 @@ export async function getFundedClient(storage: Storage): Promise<IdentityClient>

return identityClient;
}

export class SendZeroCoinTx implements Transaction<string> {
recipient: string;

constructor(recipient: string) {
this.recipient = recipient;
}

async buildProgrammableTransaction(client: CoreClientReadOnly): Promise<Uint8Array> {
const ptb = new SdkTransaction();

const recipientAddress = ptb.pure.address(this.recipient);
const zeroCoin = ptb.moveCall({ target: "0x2::coin::zero", typeArguments: ["0x2::iota::IOTA"] });

ptb.transferObjects([zeroCoin], recipientAddress);

const tx_bytes = await ptb
.build({ onlyTransactionKind: true });
return tx_bytes.slice(1);
}

async apply(effects: TransactionEffects, client: CoreClientReadOnly): Promise<string> {
return effects.created![0].reference.objectId;
}
}
43 changes: 40 additions & 3 deletions bindings/wasm/identity_wasm/lib/proposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@
// SPDX-License-Identifier: Apache-2.0

import { Transaction, TransactionBuilder } from "@iota/iota-interaction-ts/transaction_internal";
import { ConfigChange, ControllerToken, IdentityClient, OnChainIdentity, SendAction, UpdateDid } from "~identity_wasm";
import {
AccessSubIdentity,
Borrow,
ConfigChange,
ControllerExecution,
ControllerToken,
OnChainIdentity,
SendAction,
UpdateDid,
} from "~identity_wasm";

export type Action = UpdateDid | SendAction | ConfigChange;
export type Action = UpdateDid | SendAction | ConfigChange | Borrow | ControllerExecution;
export type ProposalOutput<A extends Action> = A extends UpdateDid ? void
: A extends SendAction ? void
: A extends ConfigChange ? void
: A extends Borrow ? void
: A extends ControllerExecution ? void
: never;
export type ProposalResult<A extends Action> = ProposalOutput<A> | Proposal<A>;

Expand All @@ -24,5 +35,31 @@ export interface Proposal<A extends Action> {
identity: OnChainIdentity,
controllerToken: ControllerToken,
) => TransactionBuilder<ApproveProposal>;
intoTx: (controllerToken: ControllerToken) => TransactionBuilder<ExecuteProposal<A>>;
intoTx: (identity: OnChainIdentity, controllerToken: ControllerToken) => TransactionBuilder<ExecuteProposal<A>>;
}

export type SubAccessFn<Tx extends Transaction<unknown>> = (
subIdentity: OnChainIdentity,
token: ControllerToken,
) => Promise<Tx>;

// Augment Identity to properly support accessSubIdentity
declare module "~identity_wasm" {
interface OnChainIdentity {
accessSubIdentity<Tx extends Transaction<unknown>>(
controllerToken: ControllerToken,
subIdentity: OnChainIdentity,
subFn?: SubAccessFn<Tx>,
expiration?: bigint,
): TransactionBuilder<Transaction<AccessSubIdentityProposal | Awaited<ReturnType<Tx["apply"]>>>>;
}

interface AccessSubIdentityProposal {
intoTx<Tx extends Transaction<unknown>>(
identity: OnChainIdentity,
identityToken: ControllerToken,
subIdentity: OnChainIdentity,
subAccessFn: SubAccessFn<Tx>
): TransactionBuilder<Tx>;
}
}
3 changes: 2 additions & 1 deletion bindings/wasm/identity_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
ProposalResult,
DelegatePermissions,
CoreClient,
CoreClientReadOnly
CoreClientReadOnly,
SubAccessFn
} from '../lib/index';
"#;
Loading
Loading